有关vue中key的使用

关于key

上一张讲了vue的patch过程 有一点讲的还是很含糊的 就是key相关的
提到key这概念 在vue1.0中它的前身好像是v-track 作用不用说都知道 是为了优化diff操作 那它到底是如何做到这一点的呢
我们先看看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (isUndef(oldKeyToIdx)) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); }
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
} else {
vnodeToMove = oldCh[idxInOld];
// 为什么不建议使用数组下标作为key 这里会判断出不是相同节点 并且会重新创建一个新节点
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);
}
}
newStartVnode = newCh[++newStartIdx];

这段代码是updateChildren方法里的一个if-branch 上一章节有提到这部分 我就不多赘述了
这段代码是干嘛的呢 先是通过oldVnodeChildren计算出老的子节点对应的key 每次updateChildren只需要计算一次

1
2
3
4
5
6
7
8
9
function createKeyToOldIdx (children, beginIdx, endIdx) {
var i, key;
var map = {};
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key;
if (isDef(key)) { map[key] = i; }
}
return map
}

也就是说如果一个节点有key 就把这个key当做map里的key值 把这个节点所处的index 当做value
比如一群子节点A, B, C, D key分别是’A’, ‘B’, ‘C’, ‘D’
生成的oldKeyToIdx就是

1
2
3
4
5
6
{
A: 0,
B: 1,
C: 2,
D: 3,
}

这样就算这些子节点的顺序变了 但是key没变 很容易能找到它在oldVnodesChildren中对应的节点 所以这才作为性能优化的一种手段

那为什么不推荐使用数组下标作为作为key呢
我们看看用数组下标作为key的oldKeyToIdx

1
2
3
4
5
6
{
0: 0,
1: 1,
2: 2,
3: 3,
}

这样如果子节点顺序发生了变化 比如变成了BADC 此时B的key由1变成了0 实际上我们在oldVnodesChildren中找到的是A
这时候如果A和B经过sameVnode判断不是同一个节点 会调用createElm创建新节点 那我们岂不是得不偿失啦 这样的key就没有意义了