362 lines
11 KiB
Markdown
362 lines
11 KiB
Markdown
# 一、Vue3 整体架构图
|
||
```mermaid
|
||
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 响应式系统
|
||
```mermaid
|
||
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
|
||
```mermaid
|
||
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 编译器
|
||
```mermaid
|
||
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 渲染器
|
||
```mermaid
|
||
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 初始化阶段
|
||
  流程:
|
||
1. 调用`createApp()`创建应用实例
|
||
2. 创建平台专属渲染器(DOM/SSR/Test)
|
||
3. 初始化应用上下文和插件系统
|
||
|
||
  关键代码:
|
||
```typescript
|
||
// 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 组件挂载阶段
|
||
  流程:
|
||
1. 标准化容器(字符串选择器 → DOM元素)
|
||
2. 创建根组件虚拟节点(vnode)
|
||
3. 初始化组件:解析props和slots、执行setup函数、建立响应式连接等
|
||
4. 建立渲染effect,为组件创建响应式的副作用函数
|
||
|
||
  关键代码:
|
||
```typescript
|
||
// 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 响应式更新阶段
|
||
  流程:
|
||
1. 数据变更触发Proxy的set拦截
|
||
2. 通过trigger触发依赖的effect
|
||
3. 调度器将更新任务加入队列
|
||
4. 下一个tick执行队列中的任务
|
||
5. 执行组件render函数生成新vnode
|
||
6. 调用patch进行差异更新
|
||
|
||
  关键代码:
|
||
```typescript
|
||
// 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阶段
|
||
  流程:
|
||
1. 比较新旧vnode的类型
|
||
2. 不同类型:卸载旧节点,挂载新节点
|
||
3. 相同类型:更新节点
|
||
4. 子节点对比采用高效diff算法
|
||
|
||
  关键代码:
|
||
```typescript
|
||
// 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 组件卸载阶段
|
||
  流程:
|
||
1. 触发beforeUnmount生命周期钩子
|
||
2. 停止组件渲染effect
|
||
3. 递归卸载子组件
|
||
4. 移除DOM节点
|
||
5. 触发unmounted生命周期钩子
|
||
6. 清理响应式依赖
|
||
|
||
  关键代码:
|
||
```typescript
|
||
// 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 编译阶段
|
||
  流程:
|
||
1. 将模板解析为AST
|
||
2. 转换AST(静态提升/标记PatchFlag)
|
||
3. 生成可执行的渲染函数代码
|
||
4. 运行时直接使用优化后的渲染函数
|
||
|
||
  关键代码:
|
||
```typescript
|
||
// 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}}` // 生成渲染函数
|
||
}
|
||
}
|
||
``` |