弹框prop改变时初始化弹框内表单数据、关闭弹框时重置数据之间的矛盾引发的线上问题分析

线上问题

本周五下午的时候,正在排查一个打包后 2 行样式代码变 1 行的问题,突然被拉进一个新群,有人反馈说是提交表单的一个 id 字段与预期不符,而后端排查到在页面上那个 id 是不可更改的,怀疑前端传参错误。我心里暗暗一惊:又来 bug 了!

一开始真没看懂哪里会改错那个参数,毕竟在那个弹框文件内,只能搜到一行代码会对那个id进行赋值,该代码写在 watch 中,监听的是一个 prop,该回调如果触发,外部对象必然已经发生改变。

不过后面自己还是复现了,并且写了个demo,大家可以直接访问这个链接复现问题。

image.png

复现步骤

  1. 刚打开页面的时候,App.vue中injectData.id为 1,然后打开一次弹框

此时传进去的是 1,弹框内监听了传进来的对象,且立即执行,所以表单展示也是 1,正常
image.png

  1. 随便填写内容(可不填),然后关闭弹框

此时执行关闭弹框的回调,回调内调用了表单的resetFields方法(这里就是一开始排查没有注意到的地方)
image.png

  1. 点击修改 id 的按钮

此时必然触发弹框内的 watch 回调

  1. 再次打开弹框

此时传进去的是 2(watch 回调函数中更新),且content字段是空的(第 2 步中表单重置),正常

  1. 随便填写内容(可不填),然后关闭弹框

此时再次执行关闭弹框的回调,表单再次重置

  1. 不要修改 id,直接再次打开弹框

此时 id 为 1,不正常

排查源码

根据上述步骤,就开始怀疑表单的resetFields方法的问题了,于是看源码:

image.png
发现会遍历this.fields,调用内部的resetField方法。于是又想着:

  1. this.fields是什么?
  2. resetField方法是如何实现的?

第一个问题的答案还是很好找的,就在同一个文件内:

image.png

原来this.fields的每一项都是在el.form.addField事件触发时新增的,于是全局搜索el.form.addField,很好找,只有一个地方会触发:

image.png

原来是表单项 mounted 的时候会触发这个事件,并且参数就是这个表单项,也就是说this.fields的每一项,都是表单项。

那么就继续查表单项内的resetField方法:

image.png

原来是每个表单项都会保存this.initialValue,重置的时候就重新更新为该值。

那么this.initialValue从哪里来?

其实还是上面那个表单项的mounted

image.png

好了,现在可以知道:

当我关闭弹框的时候,会调用表单组件的resetFields方法,该方法会把表单项的值重置为表单项mounted的时候的值。

那么表单项mounted的时候,是什么值?

看表现其实可以看出来,就是第一次打开弹框时的值。刚打开页面的时候,injectData.id 是 1,如果一开始先修改injectData.id为 2,再按照上述步骤复现,会发现最后的injectData.id是 2.

反正都到这一步了,还是继续看el-dialog的源码吧:

image.png
弹框内的主体部分,有个 v-if,太熟悉了,如果变量为 false,是根本不会渲染的。那么这个值到底是不是false呢?

文件内查找,只能查找到 2 处地方。

image.png

一处是上面那里,另一处就是mounted中:
image.png

一开始刷新页面的时候,this.visiblefalse的,所以这个 if 进不去,this.rendered肯定是undefined,也就不会渲染了,但是 this.visible改变的时候也没看到会重新设置这个值呀,好神奇,然后我打了好多断点,才发现是el-dialog组件通过 minxin 的形式引入了el-popup,这里会调用open方法:

image.png

就是这个open方法,会修改rendered,于是就渲染了:

image.png

修复 BUG

挺累的,查了半天又写了半天……

一开始我觉得是表单组件的resetFields方法的问题,要是该方法能重置成我刚开始传入的数据就好了,不过试了下,即使业务组件中的data里是空值,由于watch里设置了immediate,也还是会在表单项组件mounted之前执行的,所以表单项拿到的数据一开始就是有值的。而如果不设置immediate,那么第一次打开必然会异常,这种情况当初开发的时候可以测出来,但是最后可能还是通过加上immediate的方式进行解决……

修复方式有:

  1. 把监听 propData 改为监听 dialogVisible,每次打开弹框时均初始化数据,关闭时无需做任何操作

目前比较倾向的方案

  1. 关闭时不调用 form 组件的内置 resetFields 函数,而是手动有选择地重置部分字段

总觉得有点繁琐

  1. 不使用 watch,在模板和提交时都手动合并 formpropData 的数据

但是我又觉得这样比较分离,总是想合起来

  1. 父组件不传prop,子组件不监听,父组件打开弹框时直接调用子组件的方法,把参数传进去初始化

从一些开源库看到的,但是感觉调私有方法好像不是太好,而且也是打开弹框时都会执行,执行频率与方法1相同

vue-next-adminvue-admin-better

  1. 设置弹框关闭时即销毁destroy-on-close,每次打开弹框都重新执行一次生命周期

也是从开源库看到的。

vue-element-plus-admin 有个监听prop的操作,并且在弹框组件封装处配置了destroy-on-close

大家如果有更好的办法也可以提出来~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值