vue中把Diff的过程叫做patch过程,patch的过程就是以新的VNode为基准,去更新旧的VNode。
patch过程中,如果面对当前VNode存在有很多chidren的情况,那么需要分别遍历patch新的children Vnode和老的 children VNode。
patch 源码:
// 判断n1和n2是否是相似节点,type一样 并且 key一样
function isSameVNodeType(n1, n2) {
if (n2.shapeFlag & 6 /* COMPONENT */ && hmrDirtyComponents.has(n2.type)) {
return false
}
return n1.type === n2.type && n1.key === n2.key
}
const patch = (
n1, // 旧vnode
n2, // 新vnode
container,
anchor = null, // 定位锚点dom,用于往锚点前插节点
parentComponent = null,
parentSuspense = null,
isSVG = false,
optimized = false // 是否启用diff优化
) => {
// 存在旧的VNode,并且新的VNode类型(key|type)与之不同,销毁对应的旧结点
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1);
//n1设置为null,保证后面走整个节点的mount逻辑
n1 = null;
}
if (n2.patchFlag === -2 /* BAIL */) {
optimized = false;
n2.dynamicChildren = null;
}
/**
patch内部有两种情况:
1. 挂载 n1 为null
2. 更新
*/
const { type, ref, shapeFlag } = n2;
switch (type) {
case Text: //处理文本
processText(n1, n2, container, anchor);
break;
case Comment: //处理注释
processCommentNode(n1, n2, container, anchor);
break;
case Static: //处理静态节点
if (n1 == null) {
mountStaticNode(n2, container, anchor, isSVG);
}
else {
patchStaticNode(n1, n2, container, isSVG);
}
break;
case Fragment: //处理Fragment元素</>
processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
break;
default: //elemment 处理DOM元素
if (shapeFlag & 1 /* ELEMENT */) {
// 处理element
processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
}
else if (shapeFlag & 6 /* COMPONENT */) {
// 处理组件
processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
}
else if (shapeFlag & 64 /* TELEPORT */) {
// 处理teleport
type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals);
}
else if ( shapeFlag & 128 /* SUSPENSE */) {
// 处理 suspense
type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals);
}
else {
warn('Invalid VNode type:', type, `(${typeof type})`);
}
}
// set ref
if (ref != null && parentComponent) {
setRef(ref, n1 && n1.ref, parentSuspense, n2);
}
}
解释:n1
旧节点;n2
新节点;
总结:
- n1 和 n2 不是相似节点,设置n1=null,在子方法标识是新节点,走
mount
而不是更新。 - 判断 n2 类型,执行对应节点的操作方法
- text 文本
- Comment 注释
- Static 静态节点
- Fragment 碎片节点
- Element
- Component 组件或函数组件
- Teleport
- Suspense