上文我们分析到,app.mount("#app")
会执行mount函数,mount函数创建虚拟节点之后就会调用render函数,而render函数内部的核心就是patch函数,也就是大名鼎鼎的diff算法。
源码注释
js
const patch: PatchFn = (
n1, // 旧的虚拟节点
n2, // 新的虚拟节点
container, // 容器元素
anchor = null, // 锚点元素
parentComponent = null, // 父组件实例
parentSuspense = null, // 父 Suspense 组件
namespace = undefined, // 命名空间
slotScopeIds = null, // 插槽作用域 ID
optimized = false // 是否优化
) => {
// 1. 处理新旧节点类型不同的情况
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1);
unmount(n1, parentComponent, parentSuspense, true);
n1 = null;
}
// 2. 根据新节点的类型选择不同的处理方式
const { type, 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);
}
break;
case Fragment:
// 处理片段节点
processFragment(/*...参数...*/);
break;
default:
// 3. 处理组件或元素节点
if (shapeFlag & ShapeFlags.ELEMENT) {
// 处理普通元素
processElement(/*...参数...*/);
} else if (shapeFlag & ShapeFlags.COMPONENT) {
// 处理组件
processComponent(/*...参数...*/);
}
}
};
整体流程
patch 函数执行过程主要分为三个阶段:
- 差异检测阶段
- 检查新旧节点类型是否相同
- 如果不同则卸载旧节点
- 类型识别阶段
- 获取新节点的类型和形状标志
- 根据类型进行分发处理
- 具体处理阶段
- 根据不同节点类型调用对应的处理函数
- 执行实际的 DOM 操作
关键步骤解析
1. 差异检测阶段
js
// 示例:组件更新场景
// 旧节点是 div 元素
const n1 = h("div", { class: "old" });
// 新节点是组件
const n2 = h(Component);
// 检测到类型不同,执行卸载流程
if (n1 && !isSameVNodeType(n1, n2)) {
// 1. 获取下一个节点作为锚点
anchor = getNextHostNode(n1);
// 2. 卸载旧节点
unmount(n1, parentComponent, parentSuspense, true);
// 3. 重置旧节点引用
n1 = null;
}
2. 类型识别阶段
js
// 获取节点类型和形状标志
const { type, shapeFlag } = n2;
// 示例:不同类型的节点识别
switch (type) {
case Text:
// 文本节点
processText(n1, n2, container, anchor);
break;
case Static:
// 静态节点
if (n1 == null) mountStaticNode(n2, container, anchor);
break;
default:
// 根据 shapeFlag 判断是元素还是组件
if (shapeFlag & ShapeFlags.ELEMENT) {
// 元素节点
processElement(/*...参数...*/);
} else if (shapeFlag & ShapeFlags.COMPONENT) {
// 组件节点
processComponent(/*...参数...*/);
}
}
3. 具体处理阶段
js
// 示例:元素节点的处理流程
const processElement = (n1, n2, container, anchor) => {
if (n1 == null) {
// 挂载新元素
mountElement(n2, container, anchor);
} else {
// 更新已有元素
patchElement(n1, n2);
}
};
// 示例:组件节点的处理流程
const processComponent = (n1, n2, container) => {
if (n1 == null) {
// 挂载新组件
mountComponent(n2, container);
} else {
// 更新已有组件
updateComponent(n1, n2);
}
};
总结
本文,通过分析我们得知,patch函数也就是diff算法总体分为差异检查、类型识别、具体处理这三个阶段。
待深入分析的函数
-
processElement
- 元素的创建和更新逻辑
- 属性的 diff 和更新
- 子节点的处理
-
processComponent
- 组件的初始化流程
- 组件的更新机制
- 生命周期的处理
-
patchChildren
- 子节点的 diff 算法
- key 的作用机制
- 列表更新优化