mount 和 patch 通过递归调用、类型分发与子节点遍历构成统一渲染链:mount 从根节点深度挂载,patch 基于双树比对条件递归更新,二者均以 children 为驱动,遇叶子节点或空 children 终止。

Vue.js 渲染器中,mount 和 patch 是两个核心函数,它们共同构成虚拟 DOM 的首次挂载与后续更新流程。二者并非独立运行,而是通过**递归调用 + 类型分发 + 子节点遍历**形成统一的执行链条。理解其递归逻辑,关键在于把握“谁调谁”“何时递归”“递归边界”三个维度。
mount 函数:从根节点开始的深度递归挂载
mount 负责将虚拟节点(VNode)首次渲染为真实 DOM 并插入容器。它本身不直接操作 DOM,而是根据 VNode 类型分发到对应挂载函数(如 mountElement、mountComponent),再由这些函数内部触发子节点的递归挂载。
- 遇到普通元素节点(
type === string),调用mountElement→ 创建 DOM 元素 → 遍历children→ 对每个子 VNode 调用mount - 遇到组件节点(
type是对象或函数),调用mountComponent→ 创建组件实例 → 触发组件setup和render→ 得到子 VNode → 再次调用mount挂载子树 - 遇到文本节点(
type === Text),直接创建文本节点并插入,不递归
patch 函数:双树比对中的条件递归更新
patch 在响应式数据变化后被调用,用于对比新旧 VNode 并更新真实 DOM。它同样按类型分发,但递归行为取决于“是否需要更新子节点”:
- 元素节点:若新旧节点
type和key相同,进入“就地更新”流程 → 先 patch 属性/事件 → 再根据子节点类型决定是否递归:
• 新旧都有children→ 进入patchChildren,按模式(text / array / mixed)分别处理,并在遍历过程中对可复用的子节点再次调用patch
• 旧有 children、新无 → 卸载全部旧子节点(不递归 mount,而是 unmount)
• 新有 children、旧无 → 对每个新子节点调用mount - 组件节点:先判断是否需更新组件实例(props / slots 等)→ 若需更新,则调用组件实例的
update方法 → 组件重新 render 得到新子 VNode → 对新子树调用patch(可能再次进入 mount 或 patch 分支)
递归的统一入口与终止条件
整个渲染流程的递归始终围绕一个入口函数展开:在 Vue 3 中是 patch(mount 实际也是通过 patch(null, n2, container) 启动)。递归的启动与延续完全由当前 VNode 的 children 字段驱动:
立即学习“前端免费学习笔记(深入)”;
-
启动点:根 VNode 的
patch调用 -
延续点:当子节点存在且需挂载/更新时,对每个子 VNode 显式调用
patch或mount -
终止点:遇到没有
children的叶子节点(如纯文本、空 Fragment、无子节点的元素),或children为null/undefined,递归自然停止
关键细节:递归不是无脑遍历,而是受控分支
很多人误以为 mount/patch 是简单 for 循环递归,实际上每次调用前都经过严格判断:
- 是否跳过更新(
dynamicChildren优化、v-once标记) - 是否需要卸载旧子树(
unmount逻辑常与递归并存,但方向相反) - 是否启用静态提升(
hoistStatic后,静态子树在 patch 阶段直接跳过,不进入递归) - Fragment 节点会扁平化子节点,使递归跳过中间层,直接作用于真实子元素










