前几天我写了一篇文章,sortable.js——Vue 数据更新问题 ,当时本身只是数据的强制刷新角度去分析,并且并没找到真正的“元凶”。javascript
很感谢有人帮我指出,多是 Vue
的 key
值,致使数据渲染不正确的。由此,我作了进一步的尝试。前端
index
做为 key
不知道你在写 v-for
的时候,会不会直接使用 index
做为它的 key
值,是的,我认可我会,不得不说,这真的不是一个好习惯。vue
根据上篇文章,咱们仍是用 sortable.js
做为例子讨论。如下是核心代码,其中 arrData
的值为 [1,2,3,4]java
<div id="sort">
<div v-for="(item,index) in arrData" :key="index" >
<div>{{item}}</div>
</div>
</div>
复制代码
mounted () {
let el = document.getElementById('sort')
var sortable = new Sortable(el, {
onEnd: (e) => {
const tempItem = this.arrData.splice(e.oldIndex, 1)[0]
this.arrData.splice(e.newIndex, 0, tempItem)
}
})
}
复制代码
固然一开始的时候,数据渲染确定是没有问题的node
好了,咱们来看下如下的操做: git
能够看到,我将3拖到2上面的时候,下面的数据变成了 1342,可是上面视图的仍是1234。而后我第四位置拖到第三位置的时候,下面的数据也是生效的,可是上面的数据彷佛所有错乱了。很好,咱们重现了案发现场。github
接着我改了绑定的 key
值,由于这里的例子比较特殊,咱们就认为 item
的值都不相同算法
<div id="sort">
<div v-for="(item,index) in arrData" :key="item" >
<div>{{item}}</div>
</div>
</div>
复制代码
再看效果:数组
是的,这个时候数据就彻底跟视图同步了。bash
为何?
先看官方文档中 key
的一句介绍
有相同父元素的子元素必须有独特的 key。重复的 key 会形成渲染错误。
之因此会形成上面渲染错误的状况,是由于咱们的 key
值不是独特的,好比上面的 key
值,在调整数组顺序后就每一项原来的 key
值都变了,因此致使了渲染错误。
咱们先来得出一个结论,用 index
做为 key
值是有隐患的,除非你能保证 index
始终可以可以做为一个惟一的标识
在 vue2.0
以后,咱们不写 key
的话,就会报 warning
,那也就是说官方是但愿咱们写 key
值的,那么 key
到底在 vue
中扮演了什么样的角色?
不使用 key 能够提升性能么 答案是,是的!能够!
先看官方解释:
若是不使用 key,Vue 会使用一种最大限度减小动态元素而且尽量的尝试修复/再利用相同类型元素的算法。使用 key,它会基于 key 的变化从新排列元素顺序,而且会移除 key 不存在的元素。
好比如今有一个数组 [1,2,3,4]变成了[2,1,3,4],那么没有 key
的值会采起一种“就地更新策略”,见下图。它不会移动元素节点的位置,而是直接修改元素自己,这样就节省了一部分性能
而对于有 key
值的元素,它的更新方式以下图所示。能够看到,这里它对 DOM 是移除/添加的操做,这是比较耗性能的。
居然不带 key
性能更优,为什么还要带 key 先来看一个例子,核心代码以下,这里模仿一个切换 tab
的功能,也就是切换的tab1 是1,2,3,4。tab2 是 5,6,7,8。其中有设置了一个点击设置第一项字体色为红色的功能。
那么当咱们点击tab1将字体色设置成红色以后,再切换到 tab2,咱们预期的结果是咱们第一项字体的初始颜色而不是红色,可是结果却仍是红色。
<div id="sort">
<button @click="trunToTab1">tab1</button>
<button @click="trunToTab2">tab2</button>
<div v-for="(item, index) in arrData">
<div @click="clickItem(index)" class="item">{{item}}</div>
</div>
</div>
复制代码
trunToTab1 () {
this.arrData = [1,2,3,4]
},
trunToTab2 () {
this.arrData = [5,6,7,8]
},
clickItem () {
document.getElementsByClassName('item')[0].style.color = 'red'
}
复制代码
这就超出了咱们的预期了,也就是官方文档所说的,默认模式指的就是不带 key
的状态,对于依赖于子组件状态或者临时 DOM 状态的,这种模式是不适用的。
这个默认的模式是高效的,可是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。
咱们来看带上 key
以后的效果
这就是官方文档之因此推荐咱们写 key
的缘由,根据文档的介绍,以下:
使用
key
,它会基于key
的变化从新排列元素顺序,而且会移除key
不存在的元素。 它也能够用于强制替换元素/组件而不是重复使用它。当你遇到以下场景的时候它可能会颇有用:
- 完整地触发组件的生命周期钩子
- 触发过渡
那么 Vue
底层 key
值究竟是怎么去作到以上的功能?咱们就得聊聊 diff
算法以及虚拟 DOM
了。
这里咱们不谈 diff
算法的具体,只看 key
值在其中的做用。(diff
算法有机会咱们再聊)
看 vue
源码中 src/core/vdom/patch.js
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
复制代码
咱们整理一下代码块:
// 若是有带 key
if (isUndef(oldKeyToIdx)) {
// 建立 index 表
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
}
if (isDef(newStartVnode.key)) {
// 有 key ,直接从上面建立中获取
idxInOld = oldKeyToIdx[newStartVnode.key]
} else {
// 没有key, 调用 findIdxInOld
idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
}
复制代码
那么最主要仍是 createKeyToOldIdx
和 findIdxInOld
两个函数的比较,那么他们作了什么呢?
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
复制代码
function findIdxInOld (node, oldCh, start, end) {
for (let i = start; i < end; i++) {
const c = oldCh[i]
if (isDef(c) && sameVnode(node, c)) return i
}
}
复制代码
咱们能够看到,若是咱们有 key
值,咱们就能够直接在 createKeyToOldIdx
方法中建立的 map
对象中根据咱们的 key
值,直接找到相应的值。没有 key
值,则须要遍历才能拿到。相比于遍历,映射的速度会更快。
key
值是每个 vnode
的惟一标识,依靠 key
,咱们能够更快的拿到 oldVnode
中相对应的节点。
第 1 题:写 React / Vue 项目时为何要在列表组件中写 key,其做用是什么?
欢迎你们关注个人前端大杂货铺~