背景
本人已经有多年的B端经验,在表单这一块自认为是手到擒来,但是今天却在这里栽了跟头;事情是这样的,前端用的UI库是iview,我承认我此前从未使用过这个UI库。本来愉快地到了下班时间6点钟,虽然有发布但是也可以去家里发布;但是此时测试过来提了一个bug,就刚好在下班之前提了一个bug,就这个bug让我熬到了十二点;这是一个什么样的bug呢?请听我慢慢道来;
其实就是一个非常简单的bug,就是有一个必填字段表单没有办法校验,像这样:
但是它的问题就在于,明明在本地和测试环境都是可以正常校验的,但是到了预发环境就不行了,令人崩溃;第一个令人怀疑的地方就是:预发环境与本地、测试环境的包版本不一致
线索一:预发环境与本地、测试环境的包版本不一致
很好还原这个问题,直接切换到预发分支然后重新安装一下依赖,对比一下预发环境和本地环境iview的包版本就行了;接下来开始操作,删除node_modules,然后将预发环境的package-lock.json拷贝过来;但是如果我们的预发环境是由运维管理的是用发布系统发布的,那么就没有办法做这个操作,因为预发环境的文件对我们来说都是不可见不可操作的;这种方式行不通;
接下来只能模拟一下预发环境,那就是删掉node_modules和package-lock.json重新安装依赖,这样依赖的版本有可能上升,引发一些bug,但是很遗憾并没有复现预发环境的问题;
有没有可能预发环境的版本比当前本地环境的版本还要低呢?这个就不敢想象了...没去深究。接下来看看还有什么线索;iview里面表单校验有两种方式:
第一种:在Form组件上定义rules,是一个对象类型,表示所有的表单规则
第二种:在FormItem组件上定义rules
这两种定义方式是不是有的不生效呢?本着这个怀疑我开始了实验:
线索二:表单校验的方式不生效
由于当前我使用的正是第二种方式,已经验证过了在预发环境是不生效的,所以我们直接来试验一下第一种方式就好了,注释掉FormItem上的rules然后在data上定义一个校验对象添加到Form组件上,通过漫长地等待,代码推上去了【husky的时间非常长】,然后提交Merge Request,找人合并,最后发布预发【预发发布也非常慢】,终于可以看到效果了,激动人心的时刻来到了,但是它总是那么令人失望!
结果是不生效!这个时候我就是没辙了,我的内心是无比的绝望啊,我感觉没有什么办法可以解决这个bug了!在绝望的同时肚子也饿的咕咕叫,还是干饭吧,先把肚子填饱再说!然而此时已经接近9点了!
干完饭了,也有精气神了,但是我还是无可奈何啊!此时旁边的同事听说了热情地建议道:既然是必填,那就给它一个默认值呗,这样不就不需要校验了吗?这也是一条思路,当我发现这个问题之后我老是想着怎么解决它,而不是换一种思路用其他的方式实现它,这就是思维定式,此时我如同抓住救命稻草一般马上去尝试
换一种方式实现它
回到代码,我发现设置一个默认值还是非常困难的,一方面显示这个表单和Form组件是父子组件关系,另一方面表单项是否展示来源于父组件的一个接口和子组件的一个接口,简单地说就是无法判断谁前谁后,这样设置默认值就比较困难了,尝试了一下也以失败告终;最后跟测试聊了一下,他们觉得不要给用户默认值,因为我们当前这个值只能选择一次,如果给了默认值用户稀里糊涂地确定了,后面想改也改不了,这么说还是很有道理的,只能回到之前这个问题上来,到底为什么表单校验失效了呢?
与其寻求真理不如先解决当前的问题
与其寻求真理不如先解决当前的问题,就是不让表单提交并且能够给到用户提示就行了;那么自然而然就想到了用toast提示用户哪一项有问题,让用户去填写,最后这种交互方式测试也同意了,也没有其他更好的办法,最后在午夜12点终于上线了。
事后再来探求真理
已经过去了好多天了,但是那天的发布就像一根绳子悬在我的心头,到底是什么原因导致了表单校验的失效呢?我还是想去探求真理,于是我再次打开了项目,跑了起来,这个时候我没有了当时的那个压力;终于一顿操作之后我发现我们这个表单项它有一个条件渲染,也就是说只有符合条件之后才展示,那么在组件创建之初肯定是不展示的,然后rules规则是在组件创建时进行绑定的,那么这样可能就会产生问题了,动态渲染的FormItem没有绑定上rules,验证了一下,当在Form组件中设置rules,如果表单使用v-show或者一开始就是展示的那么校验生效;如果表单一开始不展示而是等待条件满足后再展示,此时表单项不校验也不展示*必填项标记了;
那么FormItem上设置rules有没有类似的问题呢?却没有,FormItem上设置的rules会在FormItem动态渲染之后生效,那么为什么在预发环境它是失效的呢?这就是个未解之谜了!
为什么FormItem能够动态校验而Form不能,我们来分析一下大体流程:form在会监听rules的变化然后执行validate,此时formItem动态渲染它是感知不到的:
而写在FormItem上的rules则不一样,在mounted的时候FormItem会利用发布订阅者动态地将实例添加到fields数组中,当组件销毁时又会从fields中删除,这样的话就能够动态地进行表单的校验了
如下图:这是FormItem初始化和销毁的方法:
如下图:父组件会将FormItem实例添加到fields中,然后提交时遍历fields进行校验
还有一个未解之谜,FormItem中的表单子元素都是通过slot加入的,它怎么监听到的blur事件呢?这里又用到了发布订阅,首先如果rules中设置触发校验是blur事件,那么FormItem中就会监听一个自定义事件,如下:
然后在iview封装的每一个表单组件中会去绑定blur事件然后触发这个自定义事件,它巧妙地把表单元素与FormItem组件解耦了;
从上面的分析也可以看出来,iview使用了大量相似的模式,那就是插槽+发布订阅,插槽中的内容可以自由定义,但是内部的事件则无法掌控,所以通过发布订阅来感知,但是如果一旦脱离这个组件体系,那么就无法运行了
后记
其一:工欲善其事必先利其器,既然我们使用了这个iview组件,那么我们就必须对它的源码了如指掌,这样才能在开发过程中如鱼得水
其二:在发布过程中遇到问题,没有太多时间来排查问题,因此有时候可以换一种交互达到产品要求,事后再来排查问题