patchChildren根据是否存在key进行真正的diff或者直接patch。
diff作用就是在patch子vnode过程中,找到与新vnode对应的老vnode,复用真实的dom节点,避免不必要的性能开销
不存在key时,patchChildren渲染dom树时调用patchUnkeyedChildren方法
- 比较新老children的length获取最小值 然后对于公共部分,进行从新patch工作。
- 如果老节点数量大于新的节点数量 ,移除多出来的节点。
- 如果新的节点数量大于老节点的数量,从新 mountChildren新增的节点。
const patchUnkeyedChildren = (c1, c2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
c1 = c1 || EMPTY_ARR
c2 = c2 || EMPTY_ARR
const oldLength = c1.length
const newLength = c2.length
const commonLength = Math.min(oldLength, newLength)
let i
for (i = 0; i < commonLength; i++) { /* 依次遍历新老vnode进行patch */
const nextChild = (c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i]))
patch(
c1[i],
nextChild,
container,
null,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
if (oldLength > newLength) { /* 老vnode 数量大于新的vnode,删除多余的节点 */
unmountChildren(c1, parentComponent, parentSuspense, true, commonLength)
} else { /* /* 老vnode 数量小于于新的vnode,创造新的即诶安 */
mountChildren(
c2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized,
commonLength
)
}
}
存在key值时,调用patchKeyedChildren方法,diff算法核心如下:
变量定义如下:
/* c1 老的vnode c2 新的vnode */
let i = 0 /* 记录索引 */
const l2 = c2.length /* 新vnode的数量 */
let e1 = c1.length - 1 /* 老vnode 最后一个节点的索引 */
let e2 = l2 - 1 /* 新节点最后一个节点的索引 */
**
**
1、如果第一步没有patch完,立即,从后往前开始patch ,如果发现不同立即跳出循环
// 1. sync from start
// (a b) c
// (a b) d e
// 从头对比找到有相同的节点 patch ,发现不同,立即跳出
while (i <= e1 && i <= e2) {
const n1 = c1[i];
const n2 = (c2[i] = optimized
? cloneIfMounted(c2[i])
: normalizeVNode(c2[i]));
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
}
else {
break;
}
i++;
}
2、从尾向前开始diff
// 2. sync from end
// a (b c)
// d e (b c)
// 如果第一步没有patch完,立即,从后往前开始patch ,如果发现不同立即跳出循环
while (i <= e1 && i <= e2) {
const n1 = c1[e1];
const n2 = (c2[e2] = optimized
? cloneIfMounted(c2[e2])
: normalizeVNode(c2[e2]));
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
}
else {
break;
}
e1--;
e2--;
}
3、如果老节点全部被patch,新节点未patch完,创建新的vnode
// 3. common sequence + mount
// (a b)
// (a b) c
// i = 2, e1 = 1, e2 = 2
// (a b)
// c (a b)
// i = 0, e1 = -1, e2 = 0
if (i > e1) {
if (i <= e2) {
const nextPos = e2 + 1;
const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor;
while (i <= e2) {
patch(null, (c2[i] = optimized
? cloneIfMounted(c2[i])
: normalizeVNode(c2[i])), container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
i++;
}
}
}
4、如果新节点全部被patch,老节点有剩余,则卸载所有老节点
// 4. common sequence + unmount
// (a b) c
// (a b)
// i = 2, e1 = 2, e2 = 1
// a (b c)
// (b c)
// i = 0, e1 = 0, e2 = -1
else if (i > e2) {
while (i <= e1) {
unmount(c1[i], parentComponent, parentSuspense, true);
i++;
}
}
5、不确定的元素 ( 这种情况说明没有patch完相同的vnode ),我们可以接着①②的逻辑继续往下看
第一步: 通过老节点的key找到对应新节点的index:开始遍历老的节点,判断有没有key, 如果存在key通过新节点的keyToNewIndexMap找到与新节点index,如果不存在key那么会遍历剩下来的新节点试图找到对应index。
第二步:如果存在index证明有对应的老节点,那么直接复用老节点进行patch,没有找到与老节点对应的新节点,删除当前老节点。
第三步:newIndexToOldIndexMap找到对应新老节点关系。
6、移动dom元素、新增新节点
我们之前有个newIndexToOldIndexMap 记录偏移量
如果newIndexToOldIndexMap 的项 为 0,为新增节点
获取newIndexToOldIndexMap 最长稳定序列
通过该序列基础作为参照,对节点进行移动