vue列表渲染中key的作用_详解key在Vue.js列表渲染时究竟起到了什么作用

详解key在Vue.js列表渲染时究竟起到了什么作用

发布于 2020-11-19|

复制链接

摘记: Vue2+采用diff算法来进行新旧vnode的对比从而更新DOM节点。而通常在我们使用v-for这个指令的时候,Vue会要求你给循环列表的每一项添加唯一的key,那么这个key在渲染列表时究竟起到了什么作用呢?在解释这一点之前,你最好已经了解Vue的diff算法的具体原理是什么。Vue2更新真实DOM的操作主要是两种:创建 ..

Vue2+采用diff算法来进行新旧vnode的对比从而更新DOM节点。而通常在我们使用v-for这个指令的时候,Vue会要求你给循环列表的每一项添加唯一的key,那么这个key在渲染列表时究竟起到了什么作用呢?在解释这一点之前,你最好已经了解Vue的diff算法的具体原理是什么。Vue2更新真实DOM的操作主要是两种:创建新DOM节点并移除旧DOM节点和更新已存在的DOM节点,这两种方式里创建新DOM节点的开销肯定是远大于更新或移动已有的DOM节点,所以在diff中逻辑都是为了减少新的创建而更多的去复用已有DOM节点来完成DOM的更新。在新旧vnode的diff过程中,key是判断两个节点是否为同一节点的首要条件:

```javascript

// 参见Vue2源码 core/vdom/patch.js

function sameVnode (a, b) {

return (

a.key === b.key && (

(

a.tag === b.tag &&

a.isComment === b.isComment &&

isDef(a.data) === isDef(b.data) &&

sameInputType(a, b)

) || (

isTrue(a.isAsyncPlaceholder) &&

a.asyncFactory === b.asyncFactory &&

isUndef(b.asyncFactory.error)

)

)

)

}

```

值得注意的是,如果新旧vnode的key值都未定义的话那么两个key都为undefined,a.key === b.key 是成立的接下来是在updateChildren方法中,这个方法会对新旧vnode进行diff,然后将比对出的结果用来更新真实的DOM

```javascript

// 参见Vue2源码 core/vdom/patch.js

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {

...

while (oldStartIdx 设置key的可以在diff中更快速的找到对应节点,提高diff速度在updateChildren方法的while循环中,如果头尾交叉对比没有结果,即oldStartVnode存在且oldEndVnode存在且新旧children首尾四个vnode互不相同的条件下,会根据newStartVnode的key去对比oldCh数组中的key,从而找到相应oldVnode首先通过createKeyToOldIdx方法创建一个关于oldCh的map

```javascript

if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)

function createKeyToOldIdx (children, beginIdx, endIdx) {

let i, key

const map = {}

for (i = beginIdx; i 这个map中将所有定义了key的oldVnode在数组中的index值作为键值,它的key作为键名存储起来,然后赋给oldKeyToIdx

```javascript

idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)

function findIdxInOld (node, oldCh, start, end) {

for (let i = start; i 如果newStartVnode的key存在的话,就去oldKeyToIdx中寻找相同key所对应的index值,这样就能拿到跟newStartVnode的key相同的oldVnode在oldCh数组中的index,即得到了与newStartVnode对应的oldVnode。如果找不到的话,那么idxInOld就为undefined。而如果newStartVnode并没有设置key,则通过findIdxInOld方法遍历oldCh来获取与newStartVnode互为sameVnode的oldVnode,返回这个oldVnode在oldCh数组的index。(前面介绍过,Vue在更新真实DOM时倾向于真实DOM节点的复用,所以在这里还是会选择去找对应的oldVnode,来更新已有的DOM节点)这时候设置key的好处就显而易见了,有key存在时我们可以通过map映射快速定位到对应的oldVnode然后进行patch,没有key值时我们需要遍历这个oldCh数组然后去一一进行比较,相比之下肯定是key存在时diff更高效。接下来就是更新DOM的过程,如果oldCh[idxInOld]存在且与newStartVnode互为sameVnode存在则先更新再移动,否则创建新的element

```javascript

if (isUndef(idxInOld)) { // New element

createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)

} else {

vnodeToMove = oldCh[idxInOld]

if (sameVnode(vnodeToMove, newStartVnode)) {

patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)

oldCh[idxInOld] = undefined

canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)

} else {

// same key but different element. treat as new element

createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)

}

}

```

那么设置key值就一定能提高diff效率吗?答案是否定的

```javascript

`{{ i }}`

// 如果我们的数组是这样的

[1, 2, 3, 4, 5]

// 它的渲染结果是这样的

`1` // key: undefined

`2` // key: undefined

`3` // key: undefined

`4` // key: undefined

`5` // key: undefined

// 将它打乱

[4, 1, 3, 5, 2]

// 渲染结果是这样的 期间只发生了DOM节点的文本内容的更新

`4` // key: undefined

`1` // key: undefined

`3` // key: undefined

`5` // key: undefined

`2` // key: undefined

// 如果我们给这个数组每一项都设置了唯一的key

[{id: 'A', value: 1}, {id: 'B', value: 2}, {id: 'C', value: 3}, {id: 'D', value: 4}, {id: 'E', value: 5}]

// 它的渲染结果应该是这样的

`1` // key: A

`2` // key: B

`3` // key: C

`4` // key: D

`5` // key: E

// 将它打乱

[{id: 'D', value: 4}, {id: 'A', value: 1}, {id: 'C', value: 3}, {id: 'E', value: 5}, {id: 'B', value: 2}]

// 渲染结果是这样的 期间只发生了DOM节点的移动

`4` // key: D

`1` // key: A

`3` // key: C

`5` // key: E

`2` // key: B

```

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值