之前写了一个功能,通过v-for渲染数据,但是数据中存在hidden项,则对数据进行隐藏,由于数据显隐采用了v-if,且循环使用的key,用了(index+900)之类的写法,导致虚拟dom的计算量成倍上涨。
具体问题如下:
javascript
<template v-if="showFieldList">
<template v-for="(item, index) in fieldList">
<template v-if="!item.hidden">
<!-- 单选框 -->
<el-col
:span="item.span || 8"
:key="index + 900"
v-if="item.type === 'radio'"
>
......
</template>
</template>
</template>
这里犯了两个重大的错误,
第一,key作为vue虚拟dom中递归判断改变的标志,正确的使用key可以节省很多无畏的性能损失。
举个例子:
创建一个实例,2秒后往items数组插入数据
javascript
<body>
<div id="demo">
<p v-for="item in items" :key="item">{{item}}</p>
</div>
<script src="../../dist/vue.js"></script>
<script>
// 创建实例
const app = new Vue({
el: '#demo',
data: { items: ['a', 'b', 'c', 'd', 'e'] },
mounted () {
setTimeout(() => {
this.items.splice(2, 0, 'f') //
}, 2000);
},
});
</script>
</body>
在不使用key的情况,vue会进行这样的操作:
分析下整体流程:
比较A,A,相同类型的节点,进行patch,但数据相同,不发生dom操作
比较B,B,相同类型的节点,进行patch,但数据相同,不发生dom操作
比较C,F,相同类型的节点,进行patch,数据不同,发生dom操作
比较D,C,相同类型的节点,进行patch,数据不同,发生dom操作
比较E,D,相同类型的节点,进行patch,数据不同,发生dom操作
循环结束,将E插入到DOM中
一共发生了3次更新,1次插入操作
在使用key的情况:vue会进行这样的操作:
比较A,A,相同类型的节点,进行patch,但数据相同,不发生dom操作
比较B,B,相同类型的节点,进行patch,但数据相同,不发生dom操作
比较C,F,不相同类型的节点
比较E、E,相同类型的节点,进行patch,但数据相同,不发生dom操作
比较D、D,相同类型的节点,进行patch,但数据相同,不发生dom操作
比较C、C,相同类型的节点,进行patch,但数据相同,不发生dom操作
循环结束,将F插入到C之前
一共发生了0次更新,1次插入操作
通过上面两个小例子,可见设置key能够大大减少对页面的DOM操作,提高了diff效率
第二,Vue 官方强烈不建议在 v-for 内使用 v-if,因为这会 导致重复计算。改用 computed 过滤数据:
javascript
<!-- 错误做法 -->
<div v-for="item in list" v-if="item.isActive">{{ item.text }}</div>
<!-- 正确做法 -->
<div v-for="item in activeList" :key="item.id">{{ item.text }}</div>
<script>
export default {
computed: {
activeList() {
return this.list.filter(item => item.isActive);
}
}
};
</script>
最后修改为如下写法:
javascript
<template v-if="showFieldList">
<template v-for="(item, index) in activeList">
<template>
<!-- 单选框 -->
<el-col
:span="item.span || 8"
:key="item.code"
v-if="item.type === 'radio'"
>
......
</template>
</template>
</template>
activeList() {
return this.fieldList.filter(item => !item.hidden);
}
至此,性能问题基本解决。引以为鉴!