入口:
接上一章的 patchKeyedChildren
函数
// 有key的情况下 比较两个子节点的差异
const patchKeyedChildren = (c1, c2, el) => {
let i = 0;
// 拿到子集的长度
let e1 = c1.length - 1;
let e2 = c2.length - 1;
// 1. sync from start
// ...
// 2. sync from end
// ...
// 3. common sequence + mount 同序列加挂载 i>e1 && i<e2
// ...
// 4. common sequence + unmount 同序列加卸载 i>e2 && i<=e1
// ...
5.乱序比对unknown sequence
// ...
}
乱序比对
c1
为老的子节点c2
为新的子节点
s1
为老的子节点的第一个比对区的下标索引s2
为新的子节点的第一个比对区的下标索引 , 都是上一节从前向后比对的i
e1
为老的子节点的最后一个比对区的下标索引e2
为新的子节点的最后一个比对区的下标索引
1、找到 新的vnode需要比对区域
后面叫:
新增比对区
,就是newVnode[i]
到newVnode[e2]
)建立key和索引的映射表(
keyToNewIndexMap
): key -> newIndex
// 乱序比对
render(
h(
'div',
{ style: { 'color': 'red' } },
[
h('li', { key: 'a' }, 'a'),
h('li', { key: 'b' }, 'b'),
h('li', { key: 'c' }, 'c'),
h('li', { key: 'd' }, 'd'),
h('li', { key: 'e' }, 'e'),
h('li', { key: 'f' }, 'f'),
h('li', { key: 'g' }, 'g'),
]
),
app
)
setTimeout(() => {
render(
h(
'div',
{ style: { 'color': 'blue', background: 'red' } },
[
h('li', { key: 'a' }, 'a'),
h('li', { key: 'b' }, 'b'),
h('li', { key: 'e' }, 'e'),
h('li', { key: 'c' }, 'c'),
h('li', { key: 'd' }, 'd'),
h('li', { key: 'h' }, 'h'),
h('li', { key: 'f' }, 'f'),
h('li', { key: 'g' }, 'g'),
]
),
app
)
},1000)
// a b [c d e] f g
// a b [e c d h] f g
// i = 2, e1 = 4, e2 = 5
const s1 = i;
const s2 = i;
// key和i的映射表
const keyToNewIndexMap = new Map();
// 循环 新增比对区 每一项 建立key和索引的映射 因为老的子集就算再多也没用
for (let i = s2; i <= e2; i++) {
const nextChild = c2[i];
keyToNewIndexMap.set(nextChild.key, i);
}
console.log(keyToNewIndexMap,'keyToNewIndexMap')
打印结果:
2、遍历老的vnode的需要比对区域
后面叫
老的比对区
看一下新的里边有没有,有的话要比较差异,没有的话要添加到列表中
老的有新的没有要删除
只是比对新老属性和子集 没有移动位置
// 新增比对区的总个数: e2 - s2 + 1
// +1是因为 在数组中 索引减去索引 会比真实元素少一个
const toBePatched = e2 - s2 + 1;
// 新增比对区 和 老的比对区 的 索引映射表;
// 以toBePatched为长度的数组 默认每一项全部是 0
// 如果数组里放的值大于0 说明path过了 不是新增的; 等于0是新增的
const newIndexToOldMapIndex = new Array(toBePatched).fill(0);
for (let i = s1; i <= e1; i++) {
const oldChild = c1[i]; // 老的比对区的children
// 用老的比对区的key 在 新增比对区索引映射表keyToNewIndexMap中 获取新的里边有没有对应的索引
let newIndex = keyToNewIndexMap.get(oldChild.key);
if (newIndex == undefined) {// 1、老的有&新的没有 直接删除
unmount(oldChild);
} else { // 2、老的有新的有 比对差异
// 需要比对的都是 老的新的都有的key 记录新的索引对应的老的索引
// 如果数组里放的值大于0 说明path过了 (i + 1 为了让i=0和 默认的0 区分开)
// newIndex - s2 是因为从第s2个开始比对的
newIndexToOldMapIndex[newIndex - s2] = i + 1;
patch(oldChild, c2[newIndex], el);
}
}
console.log(newIndexToOldMapIndex, 'newIndexToOldMapIndex')
// [5, 3, 4, 0] 是0的 说明就是新增的
打印结果
3、移动位置 倒序插入
// toBePatched - 1 是 新增比对区的 "第一项索引 到 最后一项索引" 差的步数
for (let i = toBePatched - 1; i >= 0; i--) {
const nextIndex = s2 + i; // [e,c,d,h] 找到最后一个h的索引
const nextChild = c2[nextIndex]; // 找到 h
// 找到当前元素h的 下一个已比对的元素;
// nextIndex + 1 >= c2.length说明是 新的vnode的最后一项 直接push到容器中
// nextIndex + 1 < c2.length 说明要插入到下一个已比对的元素前边
let anchor = nextIndex + 1 < c2.length ? c2[nextIndex + 1].el : null;
// 当前的h是新增的没有el
// 需要看一下 当前的有没有 如果没有需要新增
// newIndexToOldMapIndex[i] 是0的 说明这是一个新元素 直接创建插入到 容器最后即可
if (newIndexToOldMapIndex[i] == 0) {
patch(null, nextChild, el, anchor)
} else {
// 插入: 根据参照物 将节点直接移动过去 所有节点都要移动 (但是有些节点可以不动)
hostInsert(nextChild.el, el, anchor);
}
}
// 目前对所有的新增节点都进行了一遍倒叙插入,其实类根据刚刚的数组来减少插入 (newIndexToOldMapIndex:[5, 3, 4, 0])
// 最长递增子序列
下一章我们来实现最长递增子序列