Vue3源码系列——key在diff中做了什么

925 阅读2分钟

在Vue和React中都有diff的存在,区别是Vue把diff控制在组件内,而React是通过Fiber优化。下面我们把diff最简化的讲出来。

diff的作用是对比新旧VNode,然后执行patch函数更新dom。在这个过程中key很重要,为了方便大家理解我对VNode做了简化。使用{key: xxx}代表VNode。

diff的目标:新建dom会消耗性能,所以oldVNode中存在的dom,尽量通过移动达到目的。不存在的再进行新建,多余的删除。

搞起吧

1、新建newVNode、oldVNode

const oldVNode = [
    { key: 1 }, { key: 2 }, { key: 3 }, { key: 4 }, { key: 5 },
    { key: 6 }, { key: 7 }, { key: 8 }, { key: 9 }, { key: 10 }
]
const newVNode = [
    { key: 1 }, { key: 10 }, { key: 9 }, { key: 5 },
    { key: 22 }, { key: 8 }, { key: 7 }, { key: 2 },
    { key: 17 }
]

2、新建map,储存oldVNode中所有的key

const map = new Map();
for (let i of oldVNode) {
    map.set(i.key, i)
}

3、使用‘双指针’方法进行对比

因为是用数组模拟,所以使用splice模拟dom移动、新建、删除。

let newIndex = 0;
let oldIndex = 0;
// ‘双指针’都不能超过界限
while (newIndex < newVNode.length && oldIndex < oldVNode.length) {
    // 当oldVNode中不存在newVNode[newIndex]时
    if (!map.has(newVNode[newIndex])) {
        // 在oldVNode中新建newVNode[newIndex]
        oldVNode.splice(newIndex, 0, newVNode[newIndex])
        // 更新指针
        newIndex++;
        oldIndex++;
    } else { // 当newVNode[newIndex]存在于oldVNode中时
        // newVNode[newIndex]和oldVNode[oldIndex]不相等
        if (newVNode[newIndex] !== oldVNode[oldIndex]) {
            // 更新oldIndex
            oldIndex++;
        } else { // newVNode[newIndex]和oldVNode[oldIndex]不相等
            // 使用下面代码,模拟dom移动
            const dom = oldVNode.splice(oldVNode, 1);
            oldVNode.splice(newIndex, 0, ...dom);
            // 更新指针
            newIndex++;
            oldIndex = newIndex;
        }
    }
}

4、因为做了‘双指针’界限,所以还要处理剩余的dom

// oldVNode有剩余,则全部删除
if (oldIndex < oldVNode.length) {
    oldVNode.splice(oldIndex)
}
// newIndex有剩余,则全部新建
if (newIndex < newVNode.length) {
    oldVNode.push(...newVNode.slice(newIndex))
}

全部代码

const oldVNode = [
    { key: 1 },
    { key: 2 },
    { key: 3 },
    { key: 4 },
    { key: 5 },
    { key: 6 },
    { key: 7 },
    { key: 8 },
    { key: 9 },
    { key: 10 }
]
const newVNode = [
    { key: 1 },
    { key: 10 },
    { key: 9 },
    { key: 5 },
    { key: 22 },
    { key: 8 },
    { key: 7 },
    { key: 2 },
    { key: 17 },
]
const map = new Map();
for (let i of oldVNode) {
    map.set(i.key, i)
}
let newIndex = 0;
let oldIndex = 0;
while (newIndex < newVNode.length && oldIndex < oldVNode.length) {
    if (!map.has(newVNode[newIndex])) {
        oldVNode.splice(newIndex, 0, newVNode[newIndex])
        newIndex++;
        oldIndex++;
    } else {
        if (newVNode[newIndex] !== oldVNode[oldIndex]) {
            oldIndex++;
        } else {
            const dom = oldVNode.splice(oldVNode, 1);
            oldVNode.splice(newIndex, 0, ...dom);
            newIndex++;
            oldIndex = newIndex;
        }
    }
}
if (oldIndex < oldVNode.length) {
    oldVNode.splice(oldIndex)
}
if (newIndex < newVNode.length) {
    oldVNode.push(...newVNode.slice(newIndex))
}

console.log(oldVNode)
console.log(newVNode)