结论:
- 使用index 作为key时, 当逆序添加或者逆序删除的时候会破坏数据结构,从而产生没必要的dom更新,导致效率低。
- 用index作为key时,如果结构中包含输入类的dom时,会产生错误的dom更新。
- 使用index和其他值拼接作为key时,和使用index作为key类似,每次的key都是不一样的,所以没办法做到增加效率。
- 再开发中推荐使用集合中固定的唯一标识作为key,例如唯一的id或者codeNumber等。
- 当不存在unshift的添加或者shift删除的时候可以使用index作为key,不会有什么影响,但是不推荐,万一养成习惯就不好改正了。
下面讲针对结论1和2进行简单的讲述(前提: 需要已经掌握了vue3的diff算法, 本文中没有对diff算法做解释,所以之前没有看过或者了解过diff算法的可能不适合本篇文章)。
1. 逆序添加或者删除的时候,即在集合头部添加或者删除, 会使所有new key和old key不一致,就会产生所有的dom重新渲染的情况,和没有key的效率没有啥区别。
2. 同时使用index作为key还有产生一些bug,当使用输入类的dom时,会产生错误的dom更新
先看一下没有绑定key的情况。
html:
<ul class="flex" v-for="item in state.tableData1">
<li>{{item.id}}</li>
<li class="ml-20">{{item.name}}</li>
<input class="ml-10" />
<el-input class="width-200 ml-10" v-model="item.inputValue" placeholder="请输入"></el-input>
</ul>
<el-button @click="onTestClick">测试</el-button>
js部分代码:
const state = reactive({
tableData1: [{
id: 1,
name: 'james1',
inputValue: ''
},{
id: 2,
name: 'james2',
inputValue: ''
},{
id: 3,
name: 'james3',
inputValue: ''
}]
})
const onTestClick = () => {
state.tableData1.unshift({ id: state.tableData1.length + 1, name: `james${state.tableData1.length + 1}`, inputValue: '' })
}
在输入框里输入相应的值之后,界面效果如下:
点击测试按钮之后,界面效果如下图所示;
由于elementpuls组件的v-model的原因,所有的dom节点在错位后会根据具体的值重新赋值, 但是原生的输入框没有双向绑定,所以就会保持原始的dom节点上的值,由此可见,如果没有双向绑定就会出现值的错误,有双向绑定之后,由于所有的dom重新渲染赋值,界面的效率会受到影响。
下面来看一下使用index作为key会不会也有这种情况:
HTML
<ul class="flex" v-for="(item, index) in state.tableData1" :key="index">
<li>{{item.id}}</li>
<li class="ml-20">{{item.name}}</li>
<input class="ml-10" />
<el-input class="width-200 ml-10" v-model="item.inputValue" placeholder="请输入"></el-input>
</ul>
<el-button @click="onTestClick">测试</el-button>
JS部分保持和上文的js一致
点击测试按钮后效果图如下:
和没有绑定key时一样的问题。
下面来看一下绑定了唯一固定的值作为key的情况
HTML
<ul class="flex" v-for="item in state.tableData1" :key="item.id">
<li>{{item.id}}</li>
<li class="ml-20">{{item.name}}</li>
<input class="ml-10" />
<el-input class="width-200 ml-10" v-model="item.inputValue" placeholder="请输入"></el-input>
</ul>
<el-button @click="onTestClick">测试</el-button>
效果如下:
附件:diff算法的简单梳理:
-
经过上述我们大致知道了diff算法的流程
1 从头对比找到有相同的节点 patch ,发现不同,立即跳出。2如果第一步没有patch完,立即,从后往前开始patch ,如果发现不同立即跳出循环。
3如果新的节点大于老的节点数 ,对于剩下的节点全部以新的vnode处理( 这种情况说明已经patch完相同的vnode )。
4 对于老的节点大于新的节点的情况 , 对于超出的节点全部卸载 ( 这种情况说明已经patch完相同的vnode )。
5不确定的元素( 这种情况说明没有patch完相同的vnode ) 与 3 ,4对立关系。
1 把没有比较过的新的vnode节点,通过map保存
记录已经patch的新节点的数量 patched
没有经过 path 新的节点的数量 toBePatched
建立一个数组newIndexToOldIndexMap,每个子元素都是[ 0, 0, 0, 0, 0, 0, ] 里面的数字记录老节点的索引 ,数组索引就是新节点的索引
开始遍历老节点
① 如果 toBePatched新的节点数量为0 ,那么统一卸载老的节点
② 如果,老节点的key存在 ,通过key找到对应的index
③ 如果,老节点的key不存在
1 遍历剩下的所有新节点
2 如果找到与当前老节点对应的新节点那么 ,将新节点的索引,赋值给newIndex
④ 没有找到与老节点对应的新节点,卸载当前老节点。
⑤ 如果找到与老节点对应的新节点,把老节点的索引,记录在存放新节点的数组中,
1 如果节点发生移动 记录已经移动了
2 patch新老节点 找到新的节点进行patch节点
遍历结束如果发生移动
① 根据 newIndexToOldIndexMap 新老节点索引列表找到最长稳定序列
② 对于 newIndexToOldIndexMap -item =0 证明不存在老节点 ,从新形成新的vnode
③ 对于发生移动的节点进行移动处理。