11 KiB
一、Vue3 整体架构图
graph TD
A[Vue 3] --> B[Compiler]
A --> C[Runtime]
A --> D[Reactivity]
B --> B1[Parser]
B --> B2[Transformer]
B --> B3[Codegen]
C --> C1[Virtual DOM]
C --> C2[Renderer]
C --> C3[Component]
D --> D1[Proxy]
D --> D2[Effect]
D --> D3[Dependency Graph]
B3 -->|生成| C1
C1 -->|Diff/Patch| C2
D3 -->|驱动更新| C1
1.1 编译器模块
编译器负责将模板转换为可执行的渲染函数,包含解析器(Parser)、转换器(Transformer)和代码生成器(Codegen) 三个子模块。
- compiler-core: 核心编译器,负责基础的编译功能,如生成抽象语法树(AST)、基础的模板编译等,是平台无关的编译器核心。
- compiler-dom: 基于compiler-core,针对浏览器环境的编译模块,重写了编译和解析方法,处理与原生 DOM API 相关的编译工作。
- compiler-sfc: 用于处理 Vue 单文件组件(.vue)的编译,将单文件组件中的模板、脚本和样式进行解析和编译。
- compiler-ssr:服务端渲染的编译模块,负责在服务端将 Vue 组件编译为适合服务器端渲染的代码。
1.2 运行时模块
运行时系统处理虚拟DOM的创建、更新和渲染,包含组件实例管理、虚拟DOM diff算法和平台渲染器。
- runtime-core:运行时核心模块,包含了 Vue 组件的生命周期、虚拟节点(vnode)、观察者(watch)等核心 API,是平台无关的运行时核心。
- runtime-dom:运行时与 DOM 相关的核心模块,提供了与浏览器 DOM 交互的功能,如创建应用实例、操作 DOM 元素等。
1.3 响应式系统
响应式系统基于Proxy实现数据劫持,通过依赖收集和触发更新机制驱动视图变化。
三大系统协同工作:编译器生成的渲染函数由运行时执行,响应式系统追踪数据变化触发组件更新,形成完整的渲染闭环。
二、基础模块
2.1 响应式系统
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(原始对象):::process -->|Proxy 包装| B(响应式对象):::process
B -->|属性读取| C(依赖收集):::process
B -->|属性修改| D(触发更新):::process
C --> E(副作用函数):::process
D --> E
E -->|更新视图| F(真实 DOM):::process
Vue3的响应式系统通过Proxy代理对象属性,在get操作时通过track函数收集当前正在执行的副作用(如组件渲染函数)作为依赖,存储到依赖关系图中。当set操作发生时,trigger函数根据依赖关系图找到所有关联的副作用函数,通过调度器批量执行。
这种设计使得数据变化能精准触发组件更新,同时通过异步批处理避免重复渲染。
响应式系统独立于其他模块,可单独使用,为Vue3提供了高效的状态管理能力。
2.2 虚拟 DOM
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(旧虚拟 DOM 树):::process -->|Diff 算法| B(差异比较):::process
C(新虚拟 DOM 树):::process --> B
B -->|更新操作| D(真实 DOM):::process
当组件更新时,Vue会比较新旧虚拟DOM树的差异。通过Patch Flags快速识别动态节点类型(如文本、样式等),仅对标记为动态的部分进行比较;对于带key的子节点列表,使用最长递增子序列算法最小化DOM操作;完全静态的子树则直接跳过diff过程。
这种靶向更新策略将传统虚拟DOM算法O(n³)的时间复杂度优化到接近O(n),同时配合异步批量更新机制,确保复杂应用也能保持流畅渲染
2.3 编译器
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(模板字符串):::process -->|解析| B(抽象语法树 - AST):::process
B -->|转换| C(优化后的 AST):::process
C -->|生成| D(渲染函数代码):::process
Vue编译器将模板字符串转换为渲染函数,经历解析、转换和代码生成三个阶段。
解析阶段通过词法分析生成原始AST抽象语法树;转换阶段对AST进行优化,包括静态节点提升(将不变节点提取到渲染函数外部)、补丁标记(给动态节点添加优化标识)、树结构打平(减少动态子节点的嵌套层级);最后代码生成阶段输出优化的渲染函数。
这些优化使得运行时能跳过静态内容比较,直接定位动态节点,大幅提升渲染性能。
2.4 渲染器
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(虚拟 DOM 树):::process -->|创建节点| B(真实 DOM 节点):::process
B -->|插入节点| C(页面):::process
A -->|更新节点| B
Vue渲染器是连接虚拟DOM与真实平台的桥梁,分为虚拟DOM处理层、Patch算法层和平台操作层。
它通过抽象平台API(如createElement、insert等)实现跨平台能力,开发者可配置不同平台的底层操作来支持Web、Canvas甚至原生应用。
渲染器内部采用增量更新策略,结合编译器提供的优化提示(如静态标记、动态子节点索引),确保DOM操作最精简。
这种设计使Vue在保证Web端高性能的同时,也能灵活适配各种渲染环境。
三、Vue3应用生命周期
3.1 初始化阶段
流程:
- 调用
createApp()创建应用实例 - 创建平台专属渲染器(DOM/SSR/Test)
- 初始化应用上下文和插件系统
关键代码:
// runtime-dom/index.ts
const renderer = createRenderer({
patchProp, // DOM属性操作
...nodeOps // DOM节点操作集合
})
function createApp(rootComponent) {
const context = createAppContext()
const app = {
_component: rootComponent,
mount(container) {
const vnode = createVNode(rootComponent)
renderer.render(vnode, container)
return vnode.component.proxy
}
}
return app
}
3.2 组件挂载阶段
流程:
- 标准化容器(字符串选择器 → DOM元素)
- 创建根组件虚拟节点(vnode)
- 初始化组件:解析props和slots、执行setup函数、建立响应式连接等
- 建立渲染effect,为组件创建响应式的副作用函数
关键代码:
// runtime-core/renderer.ts
function mountComponent(vnode, container) {
// 1. 创建组件实例
const instance = createComponentInstance(vnode)
// 2. 初始化组件
setupComponent(instance)
// 3. 建立渲染effect
setupRenderEffect(instance, container)
}
function setupRenderEffect(instance, container) {
instance.update = effect(() => {
if (!instance.isMounted) {
// 首次渲染
const subTree = (instance.subTree = renderComponentRoot(instance))
patch(null, subTree, container)
instance.isMounted = true
} else {
// 更新渲染
const nextTree = renderComponentRoot(instance)
patch(instance.subTree, nextTree, container)
instance.subTree = nextTree
}
}, { scheduler: queueJob })
}
3.3 响应式更新阶段
流程:
- 数据变更触发Proxy的set拦截
- 通过trigger触发依赖的effect
- 调度器将更新任务加入队列
- 下一个tick执行队列中的任务
- 执行组件render函数生成新vnode
- 调用patch进行差异更新
关键代码:
// reactivity/reactive.ts
function createReactiveObject(target) {
return new Proxy(target, {
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (hasChanged(value, oldValue)) {
trigger(target, key) // 触发更新
}
return result
}
})
}
// runtime-core/scheduler.ts
const queue = []
function queueJob(job) {
if (!queue.includes(job)) {
queue.push(job)
queueFlush()
}
}
function queueFlush() {
if (!isFlushing) {
nextTick(flushJobs)
}
}
function flushJobs() {
isFlushing = true
queue.sort((a, b) => a.id - b.id) // 保证父组件先更新
for (let i = 0; i < queue.length; i++) {
queue[i]()
}
queue.length = 0
isFlushing = false
}
3.4 虚拟DOM Patch阶段
流程:
- 比较新旧vnode的类型
- 不同类型:卸载旧节点,挂载新节点
- 相同类型:更新节点
- 子节点对比采用高效diff算法
关键代码:
// runtime-core/renderer.ts
function patch(n1, n2, container) {
// 1. 类型不同直接卸载
if (n1 && !isSameVNodeType(n1, n2)) {
unmount(n1)
n1 = null
}
const { type, patchFlag } = n2
// 2. 根据类型处理
switch (type) {
case Text:
processText(n1, n2, container)
break
case Fragment:
processFragment(n1, n2, container)
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(n1, n2, container)
} else if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent(n1, n2, container)
}
}
}
function processElement(n1, n2, container) {
if (n1 == null) {
mountElement(n2, container)
} else {
patchElement(n1, n2)
}
}
function patchElement(n1, n2) {
// 根据patchFlag进行优化更新
if (n2.patchFlag & PatchFlags.FULL_PROPS) {
// 全量props更新
} else {
// 只更新动态props
}
// 更新子节点
patchChildren(n1, n2)
}
3.5 组件卸载阶段
流程:
- 触发beforeUnmount生命周期钩子
- 停止组件渲染effect
- 递归卸载子组件
- 移除DOM节点
- 触发unmounted生命周期钩子
- 清理响应式依赖
关键代码:
// runtime-core/renderer.ts
function unmount(vnode) {
if (vnode.type === Fragment) {
unmountChildren(vnode.children)
return
}
const { component } = vnode
if (component) {
unmountComponent(component)
} else {
remove(vnode.el!)
}
}
function unmountComponent(instance) {
// 1. 触发beforeUnmount
if (instance.bum) {
invokeArrayFns(instance.bum)
}
// 2. 停止响应式effect
stop(instance.update)
// 3. 递归卸载子树
unmount(instance.subTree)
// 4. 触发unmounted
if (instance.um) {
queuePostRenderEffect(instance.um)
}
// 5. 清理引用
instance.isUnmounted = true
}
3.6 编译阶段
流程:
- 将模板解析为AST
- 转换AST(静态提升/标记PatchFlag)
- 生成可执行的渲染函数代码
- 运行时直接使用优化后的渲染函数
关键代码:
// compiler-core/src/compile.ts
function baseCompile(template, options) {
// 1. 解析阶段
const ast = parse(template)
// 2. 转换阶段
transform(ast, {
hoistStatic: true, // 静态提升
nodeTransforms: [
transformIf,
transformFor,
transformExpression
]
})
// 3. 代码生成
const code = generate(ast, {
mode: 'function',
runtimeGlobalName: 'Vue',
prefixIdentifiers: true
})
return {
ast,
code: `with(this){return ${code}}` // 生成渲染函数
}
}