今天我们就来“解剖” Vue3 的渲染执行流程,从源码级别拆解每一个环节,语言尽量轻松自然,内容详尽到让你感觉像在读框架的设计文档!我们还会用图表把流程可视化,帮你一目了然地抓住重点。如果你想深入理解 Vue3 的核心机制,这篇绝对是你的菜!
一、Vue3 渲染流程总览
Vue3 的渲染流程可以概括为五个核心阶段:初始化 -> 模板编译 -> 虚拟 DOM 生成 -> 真实 DOM 渲染 -> 响应式更新。这背后涉及了组件生命周期、响应式系统、编译器、渲染器等多个模块的协作。整个流程的核心在于响应式系统(基于 Proxy 的数据监听)和渲染器(虚拟 DOM 和 Diff 算法的实现)。
下面是 Vue3 渲染流程的整体示意图:

接下来,我们从源码角度逐层拆解,带你深入 Vue3 的每一个细节。
二、详细拆解 Vue3 渲染执行流程
1. 初始化阶段:Vue3 的“开场白”
初始化是 Vue3 渲染的起点,相当于给整个应用搭好舞台。调用 createApp 时,Vue3 会完成一系列准备工作,确保后续渲染顺利进行。
1.1 创建应用实例
当你写下 createApp(App).mount('#app'),Vue3 会触发以下步骤:
创建应用上下文:createApp 函数(位于 @vue/runtime-core)会生成一个应用实例,包含全局配置(如 app.config)、插件、指令等。源码中,createApp 调用了 createAppAPI,返回一个 app 对象,结构如下:
const app = {
mount,
component,
directive,
use,
config: { ... }
}
挂载根组件:mount 方法会解析传入的根组件(通常是 App.vue),将其作为组件树的根节点。Vue3 会创建根组件的实例,并初始化其内部状态。
1.2 组件实例化
Vue3 会为每个组件创建实例(ComponentInstance),这一步涉及以下核心操作:
解析组件定义:无论是单文件组件(SFC)还是普通对象组件,Vue3 会提取其 setup 函数、模板、props、emits 等信息。
执行 setup 函数:如果使用 <script setup> 或显式的 setup 函数,Vue3 会在这里执行它,初始化组件的响应式数据、计算属性、方法等。setup 返回的对象会被绑定到组件实例的上下文中。
初始化生命周期:Vue3 为组件注册生命周期钩子(如 beforeCreate、created),并在适当的时机触发。
1.3 响应式系统启动
Vue3 的响应式系统是渲染流程的基石,基于 ES6 的 Proxy 实现。初始化阶段会为所有响应式数据(ref、reactive 等)创建 Proxy 对象:
Proxy 包装:比如 const count = ref(0) 会创建一个 RefImpl 对象,其内部通过 Proxy 监听 value 的 getter 和 setter。
依赖收集准备:Vue3 的 effect 系统会为每个响应式对象维护一个依赖列表(deps),用于后续的依赖收集和更新触发。

2. 模板编译:从模板到可执行代码
模板编译是 Vue3 将你的 <template> 代码变成可执行渲染函数的过程。这一阶段由 Vue3 的编译器(@vue/compiler-core)负责,主要分为以下步骤:
2.1 模板解析
Vue3 的编译器会将模板字符串解析成抽象语法树(AST):
词法分析:编译器扫描模板,识别标签、属性、指令、插值表达式等。比如 <div v-if="show">{{ msg }}</div> 会被分解为:
元素节点:div
指令:v-if="show"
文本节点:{{ msg }}
生成 AST:解析结果形成一棵树状结构,每个节点描述了模板中的一个部分(元素、文本、指令等)。
2.2 优化 AST
Vue3 的编译器会对 AST 进行优化,减少后续渲染的开销:
静态节点提升:识别模板中的静态内容(比如纯文本或不变的 HTML),将其“提升”到渲染函数外部,避免重复生成。源码中,这通过 hoistStatic 实现。
打 Patch Flag:为动态节点(如绑定了 v-bind 或 v-on 的节点)标记 Patch Flag,告诉渲染器哪些节点需要 Diff 时特别关注。
缓存事件:事件监听器(如 @click)会被缓存,避免重复绑定。
2.3 生成渲染函数
最终,编译器将 AST 转化为一个可执行的 render 函数。这个函数返回虚拟 DOM(VNode)。比如:
<template>
<div>{{ msg }}</div>
</template>
会被编译成类似以下的渲染函数:
import { h } from 'vue'
function render() {
return h('div', {}, [this.msg])
}
如果是单文件组件(SFC),编译过程通常在构建时由 Vite 或 Webpack 的 Vue 插件完成,生成的文件中直接包含渲染函数。
3. 虚拟 DOM 生成:描绘应用的“蓝图”
渲染函数执行后,生成虚拟 DOM(Virtual DOM),也就是一棵由 JavaScript 对象组成的树,称为 VNode 树。
3.1 VNode 结构
每个 VNode 是一个轻量级的对象,包含以下关键信息:
type:节点类型(元素标签、组件、文本等)
props:节点的属性(如 class、style、事件监听器)
children:子节点(可以是字符串或 VNode 数组)
shapeFlag:描述节点类型的标志,用于优化渲染
比如 <div class="box">{{ msg }}</div> 会生成:
{
type: 'div',
props: { class: 'box' },
children: [{ type: Symbol(Text), children: msg }],
shapeFlag: ShapeFlags.ELEMENT
}
3.2 响应式依赖收集
在生成 VNode 时,渲染函数会访问响应式数据(如 this.msg),触发 Proxy 的 getter。Vue3 的 effect 系统会记录这些依赖,形成一个“副作用”函数(effect),关联到当前的渲染函数。这样,当数据变化时,Vue3 知道需要重新运行渲染函数。
3.3 优化策略
Vue3 在生成 VNode 时会应用以下优化:
静态提升:静态节点被缓存,只生成一次。
树结构扁平化:组件树被优化为更扁平的结构,减少递归遍历的开销。
动态节点标记:通过 Patch Flag,Vue3 只关注可能变化的节点。
4. 真实 DOM 渲染:从蓝图到页面
有了 VNode 树,Vue3 的渲染器(@vue/runtime-dom)会将其转化为真实的 DOM 节点,显示在页面上。
4.1 初次渲染
渲染器递归遍历 VNode 树,执行以下操作:
创建 DOM 节点:根据 VNode 的 type 调用 document.createElement(对于元素)或实例化组件。
设置属性:通过 setElementProps 设置节点的属性(如 class、style)和事件监听器。
处理子节点:递归处理 children,将子 VNode 转为真实 DOM 并插入父节点。
挂载:最终将生成的 DOM 树挂载到指定的容器(如 #app)。
4.2 Diff 算法优化
Vue3 的 Diff 算法相比 Vue2 更高效,主要得益于以下优化:
Patch Flag:只对比标记了动态内容的节点,跳过静态节点。
最长递增子序列:在处理动态子节点列表(如 v-for)时,使用最长递增子序列算法,减少 DOM 操作。
双端 Diff:通过比较新旧 VNode 树的头尾节点,快速定位变化点。
4.3 生命周期触发
挂载完成后,Vue3 会触发组件的 mounted 生命周期钩子,表明组件已渲染到页面。
5. 响应式更新:动态更新的魔法
Vue3 的响应式系统让页面能自动响应数据变化。更新流程如下:
5.1 数据变更
当你修改响应式数据(比如 ref.value = 'new value'),Proxy 的 setter 会被触发。源码中,reactive 和 ref 的 setter 会调用 trigger 函数。
5.2 触发副作用
trigger 函数会通知所有依赖该数据的 effect 函数(包括渲染函数)。这些 effect 会重新运行,生成新的 VNode。
5.3 重新渲染
渲染器对比新旧 VNode 树,使用 Diff 算法更新真实 DOM:
精准更新:只更新变化的节点,比如文本内容或动态属性。
组件级更新:如果某个组件的 props 或 slots 变化,Vue3 会重新渲染该组件的子树。
异步更新:Vue3 使用异步队列(queueJob)来批量处理更新,避免重复渲染。

三、Vue3 渲染流程的源码级细节
为了更深入理解,我们来看几个关键源码片段(基于 @vue/runtime-core):
3.1 响应式系统的核心
reactive 的实现:
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
track(target, 'get', key) // 依赖收集
return result
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (oldValue !== value) {
trigger(target, 'set', key) // 触发更新
}
return result
}
})
}
3.2 渲染器的核心
渲染器的 patch 函数(简化的伪代码):
function patch(n1, n2, container) {
if (n1 == null) {
// 初次渲染
mountElement(n2, container)
} else {
// 更新渲染
if (n1.type !== n2.type) {
// 替换节点
unmount(n1)
mountElement(n2, container)
} else {
// Diff 属性和子节点
patchProps(n1, n2)
patchChildren(n1, n2, container)
}
}
}
四、Vue3 渲染流程的优化亮点
Vue3 在渲染流程上做了大量优化,让性能大幅提升:
静态提升:静态节点被缓存,只生成一次,源码中通过 hoistStatic 实现。
Patch Flag:动态节点被标记,Diff 时只关注这些节点,减少计算量。
事件缓存:通过 cacheHandlers 缓存事件监听器,避免重复绑定。
树结构扁平化:通过 ShapeFlags 和优化算法,减少递归开销。
编译时优化:编译器会分析模板,生成高效的渲染函数,比如内联动态绑定。
这些优化让 Vue3 在性能上比 Vue2 快 1.3~2 倍,尤其在大规模应用中表现更明显。
五、实际代码中的渲染流程
来看一个更完整的例子,感受 Vue3 的渲染流程:
<template>
<div class="container">
<h1 :class="{ active: isActive }">{{ title }}</h1>
<button @click="toggle">Toggle</button>
<ul>
<li v-for="item in list" :key="item.id">{{ item.text }}</li>
</ul>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const title = ref('Welcome to Vue3')
const isActive = ref(false)
const list = reactive([
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' }
])
function toggle() {
isActive.value = !isActive.value
title.value = isActive.value ? 'Vue3 Rocks!' : 'Welcome to Vue3'
list.push({ id: list.length + 1, text: `Item ${list.length + 1}` })
}
</script>
5.1 渲染流程分析
1.初始化:
createApp 创建应用实例,挂载到 #app。
解析 <script setup>,用 Proxy 包装 title、isActive 和 list。
触发 beforeCreate 和 created 生命周期。
2.模板编译:
<template> 被编译成渲染函数,生成类似:
import { h, Fragment } from 'vue'
function render() {
return h('div', { class: 'container' }, [
h('h1', { class: { active: this.isActive } }, this.title),
h('button', { onClick: this.toggle }, 'Toggle'),
h('ul', {}, this.list.map(item => h('li', { key: item.id }, item.text)))
])
}
静态节点(如
Toggle文本)被提升,动态节点(如:class和v-for)被标记 Patch Flag。
3.虚拟 DOM 生成:
渲染函数运行,生成 VNode 树。
访问 title、isActive 和 list 时,触发 Proxy 的 getter,收集依赖。
4.真实 DOM 渲染:
渲染器将 VNode 转为真实 DOM,挂载到页面。
触发 mounted 生命周期。
5.响应式更新:
点击按钮调用 toggle,修改 isActive、title 和 list。
Proxy 的 setter 触发,通知渲染函数重新运行。
生成新 VNode,Diff 后只更新 <h1> 的类和文本,以及 <ul> 的子节点。
5.2 性能优化点
Patch Flag::class 和 v-for 的节点被标记,Diff 只关注这些部分。
列表优化:v-for 使用 key 确保高效的子节点 Diff。
异步更新:多次数据变更被合并为一次渲染,减少 DOM 操作。
六、常见问题与调试技巧
6.1 为什么组件不更新?
问题:数据变了但页面没更新。
原因:可能数据未正确设置为响应式(比如直接修改普通对象)。
解决:确保使用 ref 或 reactive,或者用 reactive 包装对象。
6.2 性能瓶颈如何排查?
工具:使用 Vue Devtools 查看组件更新范围和渲染时间。
优化:检查是否有不必要的重复渲染,考虑使用 v-memo 或 v-once 优化静态内容。
6.3 如何深入调试?
源码调试:克隆 Vue3 源码(vuejs/core),在本地运行并设置断点。
日志:在 effect 或 render 函数中添加 console.log,追踪依赖收集和渲染过程。
七、总结
Vue3 的渲染流程是一个精密的协作系统,从 createApp 到响应式更新,每一步都经过精心设计。响应式系统(Proxy 和 effect)提供了高效的数据监听,编译器优化了模板到渲染函数的转换,渲染器的 Diff 算法和 Patch Flag 保证了 DOM 操作的最小化。理解这些细节,不仅能帮你写出更高效的代码,还能在性能优化和调试时游刃有余。