问题场景
身为一个表单表格工程师,自然日复一日的写着表单表格,本以为已经没啥难点的时候转眼间就来了一个有意思的情况,在超大量数据绑定在vue的时候出现了表单操作起来卡顿的情况。
这里先贴上本项目出现的情况演示的github上的地址,tag1.0.1
当在input
输入数据的时候,连续输入会感觉明显的延迟。
那么,这到底是怎么回事?
代码
上述的表单数据项修改频繁由后端返回,于是在前端需要渲染从后端返回的68kb的一个JSON数据串,包括所有配置表单项以及其可能的选项值,数据见这里
核心渲染是有这么一段
JavaScript
1 2 3 4 5 6 7 8 9 10 11<div class="basic-info ct-form"v-for="(config,configIndex)in formConfig":key="configIndex">
<h3class="form__title">{{config.title}}</h3>
<el-form class="form-content"ref="form"label-width="150px"> <el-form-item
class="basic-form-item"
v-for="(item,itemIndex)in config.formItems"
:key="itemIndex"
:prop="de"
:label="item.name"
:required="quired"
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
:rules="item.rules">
<el-radio-group
v-if="pe==='radio'"
v-model="de]">
<el-radio
v-for="(option,radioIndex)in formOptions[item.optionCode]"
:key="option.value"
:label="option.value"
:disabled="item.disabled">
{{option.label}}
</el-radio>
</el-radio-group>
<el-input
v-else-if="pe==='input'"
:class="{longInput:item.isLongInput}"
:placeholder="item.placeholder||'请输入'"
v-model="de]"
:label="item.label"
:disabled="item.disabled"
:maxlength="item.maxLength">
</el-input>
<el-select
v-else-if="pe==='select'"
v-model="de]"
:disabled="item.disabled"
:placeholder="item.placeholder||'请选择'">
<el-option
v-for="(option,optionsIndex)in formOptions[item.optionCode]"
:key="option.value"
:label="option.label"
:value="option.value">
</el-option>
</el-select>
</el-form-item>
</el-form>
</div>
html表格元素这就是一个简单的双层遍历渲染所有表单配置项的模版代码,其中的formConfig正是所有配置表单项,数据量极多。formOptions挂载了所有表单选项值,也是动辄几千项。
思路
正当我对着这么高的操作延时发愁的时候,组里一个大佬提醒我,可能是
Vue.prototype._update这个触发的太频繁了。
我急忙到这一段打了个断点调试
Vue.prototype._update这函数里触发的是VNode虚拟节点的比对更新,打断点调试后发现实际上这是一个循环,在控制台里输出this.$el的时候能得到正在深度遍历中的节点,沿着根结点App(也是formConfig数据绑定的作用域)开始直到具体触发输入的那个表单元素。
在本项目里是使用了遍历输出所有的表单元素,并且当前组件的作用域是直接挂在根结点上的,是否
就是这个遍历引发了如此高的延时呢?于是我到上图右侧的调用堆栈,发现正是flushSchedulerQueue函数写着一个for循环。
在flushSchedulerQueue函数中的for循环里头尾插入代码来获取耗费时间。结果得知输入时的延迟大概在300ms之上。
似乎问题就到了,flushSchedulerQueue 函数针对data 中数据的修改把watcher 推送进队列里在更新,这一循环耗费的时间比较长。
解决
其实早在调试Vue.prototype._update 函数就初见端倪,循环中的this.$el 从当前组件的根部开始深度遍历,遍历了太多次,那么只要想办法缩小当前组件所绑定的数据量就解决了。
于是核心代码调整为
12<div class ="basic-info ct-form"v -for ="(config,configIndex)in
formConfig":key ="configIndex">
<edit -form :config ="config":data ="formData":options ="formOptions"></edit -form ></div
>
只是用一个edit-form 包裹刚刚所有的el-form-item 的渲染代码就解决了,再次调试Vue.prototype._update 得出遍历节点this.$el 已经变为下图所示
的div.edit-form 了,flushSchedulerQueue 函数for 循环的延迟也变为10ms 左
右
修复版的代码在2.0.0的tag 上,这里贴上链接
后记
本质上这就是一个原则,最好不要在一个vue 组件上直接绑定如此多的数据,如果有大量数据请分多个组件绑定。这么浅尝辄止实在让人不够尽兴,于是这里贴上
Vue.prototype._update 前的关键部分调用堆栈以及其函数作用。
到项目中node_modules 下的vue.esm.js
12345678#往input 里输入将会触发model data 的更新
978set :function reactiveSetter (newVal )
#订阅器dep 是数据绑定和视图更新的关键,这里触发去通知相关视图的更新
994dep .notify ();
673Dep .prototype .notify
#notify 函数里的subs 实际上是Watcher 对象的实例,这里触发视图更新操作
677subs [i ].update ();subs 实际上是包裹watcher 的数组
3093Watcher .prototype .update
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论