# Vue 渲染系统的四个关键阶段:从模板编译到新旧 VDOM Patch 的完整机制解析

现代 UI 框架的核心目标是在数据变化时以最小代价更新视图。Vue 通过将整个渲染流程拆分为四个阶段,使得 UI 架构具备可操作性、可维护性与高性能。本文将系统介绍 Vue 中:

  • 模板如何生成渲染逻辑
  • 组件如何组织渲染过程
  • 响应式系统如何触发更新
  • 新旧 VDOM 如何对比和更新 DOM

你将清楚理解 渲染函数来自哪里,渲染 effect 如何被触发,以及新旧 VDOM 在整个生命周期中如何被获取和使用。


第一阶段:模板编译(Template Compilation)------从静态模板到可执行渲染逻辑

所有 Vue 组件都从 template 开始,但模板本质上只是字符串,Vue 无法直接执行,因此编译阶段负责将模板转化为最终的渲染函数(render)。

以一个简单模板为例:

css 复制代码
<div>{{ count }}</div>

编译器会将其解析为 AST(抽象语法树),再转换为具有结构化意义的渲染代码,最终生成的渲染函数类似:

javascript 复制代码
function render(_ctx) {
  return createElementBlock("div", null, _ctx.count, 1 /* TEXT */)
}

在这一阶段,有三项关键优化能力:

1. 静态与动态节点区分

静态节点被提升,只生成一次;动态节点会带有 PatchFlag,如 TEXT、PROPS 等,用于精确更新。

2. 渲染函数是纯 JS,不包含 DOM 操作

渲染函数只会构建 VNode(虚拟 DOM),不会直接访问浏览器 DOM。

3. 结构化信息为后续的 diff 提供优势

编译后的代码比运行时解释模板更快,可直接参与 diff 计算。

编译阶段的产物 render() 是 UI 渲染的核心入口,也是所有 VDOM 的唯一来源。


第二阶段:组件渲染 effect(Render Effect)------渲染函数的执行与 VDOM 的生成者

每个 Vue 组件实例在初次挂载时都会创建一个 渲染 effect。这是 Vue 中真正负责"渲染 UI"与"响应式驱动更新"的核心单元。

Vue 内部会执行:

ini 复制代码
instance.update = effect(() => {
  const subTree = render(instance.ctx)
  patch(instance.vnode, subTree)
  instance.vnode = subTree
})

这个渲染 effect 具备以下职责:

1. 执行渲染函数并生成新的 VNode(新 VDOM)

渲染函数访问 ctx 中的响应式数据,从而生成一套全新的虚拟 DOM 树。

2. patch 负责向真实 DOM 提交变化

渲染 effect 本身不处理 DOM,它只是负责调用 patch 让 DOM 更新。

3. effect 会在执行过程中记录依赖

访问 _ctx.xxx 时,响应式 getter 会执行 track,将渲染 effect 与数据绑定。

4. 渲染 effect 是组件级而非节点级

无论模板多大,组件始终只有一个渲染 effect。

渲染 effect 是"新 VDOM 的来源者",并同时保存上一次渲染的旧 VDOM。

这一点非常关键,下一阶段我们将看到如何利用它。


第三阶段:响应式触发(Reactive Trigger)------数据变化驱动重新渲染的机制核心

当响应式数据修改时:

复制代码
state.count++

Vue 的响应式系统会执行:

1. setter → trigger(target, key)

找到依赖该 key 的所有 effect,此处即 渲染 effect

2. 调度渲染 effect 进入队列(scheduler)

Vue 不会立即重新渲染,而是进行批处理,以优化性能。

3. 下一轮事件循环执行渲染 effect

重新执行:

scss 复制代码
render(ctx)
patch(oldVNode, newVNode)

这是一次完整的 UI 更新动作:

  • 获取新 VDOM(新 vnode)
  • 与旧 VDOM(上次 vnode)进行 diff
  • 将真实 DOM 局部更新到最新状态

响应式系统的核心作用不是更新 DOM,而是触发渲染 effect。

DOM 更新发生在下一阶段。


第四阶段:虚拟 DOM Diff 与 DOM Patch ------ 新旧 VDOM 的来源与交替

这部分是你最关心的:"新旧 VDOM 分别从哪里来?Vue 是如何获取它们并做 diff 的?"

我们先给出答案:


新 VDOM 的来源:由当前执行的 render() 生成

在渲染 effect 内:

ini 复制代码
const newVNode = render(instance.ctx)

每次执行 render,都会返回一棵全新的 VNode 树,这就是 新 VDOM

它不来自缓存、不来自 DOM,而是 render 的直接产物。


旧 VDOM 的来源:组件实例 instance.vnode 中保存的上一次结果

第一次渲染时,Vue 会执行:

scss 复制代码
instance.vnode = render(instance.ctx)
patch(null, instance.vnode)

第二次更新时:

ini 复制代码
const newVNode = render(ctx)
patch(instance.vnode, newVNode)
instance.vnode = newVNode

可以总结:

  • 旧 VDOM = 上一次渲染函数生成的 VNode,被 Vue 存储在 instance.vnodeinstance.subTree
  • 新 VDOM = 当前渲染函数生成的 VNode

它们都来自渲染函数,不通过 DOM 查询获得。

完整的生命周期结构如下:

scss 复制代码
第一次:
newVNode = render()
patch(null, newVNode)
oldVNode ← newVNode

第二次:
newVNode = render()
patch(oldVNode, newVNode)
oldVNode ← newVNode

第三次:
newVNode = render()
patch(oldVNode, newVNode)
oldVNode ← newVNode

它们在每次更新中不断交替,像接力棒一样传递。


总结:Vue 渲染系统的完整四阶段链路

最终我们可以将 Vue 的渲染管线抽象为四个连续的大阶段:

scss 复制代码
1) Template Compile
   模板 → 渲染函数(纯 JS、无 DOM)

2) Render Effect
   执行渲染函数 → 得到新 VDOM
   保存旧 VDOM → instance.vnode

3) Reactivity Trigger
   数据变化 → 触发渲染 effect 重新执行

4) Patch (Diff → DOM Update)
   patch(oldVNode, newVNode)
   最小成本更新真实 DOM

其中:

  • 新 VDOM 来自 render()
  • 旧 VDOM 来自组件实例保存的上一次 render() 结果
  • patch 是最终落实 DOM 更新的唯一阶段

这就是 Vue 数据变化后自动更新 UI 的真正内部机制。

相关推荐
狂炫冰美式6 分钟前
人均配了AI, 为什么公司还是没变快? 🤔 本质还是分布式系统问题
前端·后端·架构
乘风gg1 小时前
多 Agent 不是万能的!搞懂这 5 个原则,少走 1 年弯路!
前端·agent·ai编程
猩猩程序员2 小时前
Vercel 推出 Agent 框架 Eve:让 AI Agent 像写 Web 应用一样简单
前端
爱读源码的大都督2 小时前
Claude Code源码分析(三):为什么系统提示词中需要有tools呢?
前端·人工智能·后端
爱勇宝2 小时前
Claude Code 被曝暗藏“隐形检测”代码:封代理不是最可怕的,可怕的是你根本不知道它在干什么
前端·后端·程序员
小牛不牛的程序员2 小时前
我用 Claude Code 半天撸完了一个完整网站,AI 编程到底提升了多少效率?
前端
东风破_2 小时前
JavaScript 面试常考的字符串算法:从反转字符串到回文判断
前端·javascript
ITOM运维行者3 小时前
从零搭建企业级服务器监控体系:踩坑实录与架构设计
前端·后端
monologues3 小时前
深入 Vue 3 源码:响应式系统的精妙设计与编译优化
前端
hunterandroid3 小时前
Paging 3 分页:从手动分页到声明式加载
前端