主题
实现完整流程 - Compile 联调
本节对标 Vue 3 源码
vue主包入口 +@vue/compiler-core中的compile.ts源码位置:packages/vue/src/index.ts、packages/compiler-core/src/compile.ts
全链路概览
至此,我们已经实现了 mini-vue 的所有核心模块。本节将它们串联起来,完成从模板字符串到页面渲染的完整流程:
template (字符串)
│
▼ ─── Parse ───
│
AST (原始抽象语法树)
│
▼ ─── Transform ───
│
AST' (转换后的 AST,带 codegenNode)
│
▼ ─── Codegen ───
│
render function string (代码字符串)
│
▼ ─── new Function() ───
│
render function (可执行的渲染函数)
│
▼ ─── 执行 render() ───
│
VNode Tree (虚拟 DOM 树)
│
▼ ─── Renderer (patch) ───
│
Real DOM (真实 DOM)这条链路涉及两大系统的对接:
- 编译系统:compiler-core(parse → transform → codegen)
- 运行时系统:runtime-core + runtime-dom(createApp → render → patch → DOM)
baseCompile —— 编译器核心入口
ts
// 对标 packages/compiler-core/src/compile.ts
import { baseParse } from './parse'
import { transform } from './transform'
import { generate } from './codegen'
import { transformElement } from './transforms/transformElement'
import { transformText } from './transforms/transformText'
import { transformExpression } from './transforms/transformExpression'
// 编译器的核心入口函数:将模板字符串编译为 render 函数代码
// "base" 前缀意味着这是基础实现,不同平台可在此基础上扩展
export function baseCompile(template: string) {
// Step 1: Parse — 将模板字符串解析为 AST(抽象语法树)
const ast = baseParse(template)
// Step 2: Transform — 对 AST 进行语义分析和转换
// 通过插件化的 nodeTransforms 处理不同类型的节点
transform(ast, {
nodeTransforms: [
transformExpression, // 处理表达式(如添加 _ctx 前缀)
transformElement, // 处理元素(生成 codegenNode)
transformText, // 合并相邻的文本和插值
],
})
// Step 3: Codegen — 根据转换后的 AST 生成 render 函数代码字符串
return generate(ast)
}baseCompile 将三个阶段串联在一起。之所以叫 "base",是因为在 Vue 3 中,不同平台可以在此基础上扩展(如 SSR 编译器、SFC 编译器等)。
编译流程示例
ts
const { code } = baseCompile('<div>hi,{{ message }}</div>')
console.log(code)
// 输出:
// const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = Vue
//
// return function render(_ctx, _cache) {
// return _createElementVNode('div', null, 'hi,' + _toDisplayString(_ctx.message))
// }compileToFunction —— 代码字符串转为可执行函数
编译器生成的是代码字符串,需要通过 new Function() 转换为真正可执行的函数:
ts
// 对标 packages/vue/src/index.ts - compileToFunction
import * as runtimeDom from '@mini-vue/runtime-dom'
// 将模板字符串编译为可执行的 render 函数
// 这是编译系统和运行时系统的桥梁
function compileToFunction(template: string) {
// 调用编译器核心,得到 render 函数的代码字符串
const { code } = baseCompile(template)
// 使用 new Function 将代码字符串转为真正的函数
// 第一个参数 "Vue" 定义了工厂函数的形参名
// 第二个参数 code 是工厂函数的函数体(包含 const { ... } = Vue 和 return render)
// 立即调用工厂函数,传入 runtimeDom 作为 "Vue" 参数的实际值
const render = new Function('Vue', code)(runtimeDom)
return render // 返回编译后的 render 函数
}new Function 的工作原理
ts
// 生成的代码字符串
const code = `
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = Vue
return function render(_ctx, _cache) {
return _createElementVNode('div', null, 'hi,' + _toDisplayString(_ctx.message))
}
`
// new Function('Vue', code) 等价于创建了如下工厂函数:
const factory = function(Vue) {
// 从传入的 Vue(即 runtimeDom)中解构出辅助函数
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = Vue
// 返回 render 函数
return function render(_ctx, _cache) {
return _createElementVNode('div', null, 'hi,' + _toDisplayString(_ctx.message))
}
}
// 立即调用工厂函数,传入运行时模块,获得 render 函数
const render = factory(runtimeDom)通过这种方式,codegen 生成的代码中的 Vue 变量被绑定为 runtimeDom 模块,从而可以访问 toDisplayString、createElementVNode 等运行时辅助函数。
注册编译器:registerRuntimeCompiler
为了保持运行时和编译器的解耦,Vue 3 使用注册模式将编译器注入运行时:
ts
// 对标 packages/runtime-core/src/component.ts
// 用于存储编译器函数的变量,初始为 undefined
let compile: ((template: string) => any) | undefined
// 注册运行时编译器:允许外部将编译器函数注入到运行时
// 这种设计实现了运行时和编译器的解耦
export function registerRuntimeCompiler(_compile: any) {
compile = _compile
}ts
// 对标 packages/vue/src/index.ts
// 在应用入口注册编译器,将 compileToFunction 注入到运行时
import { registerRuntimeCompiler } from '@mini-vue/runtime-core'
// 注册后,运行时在遇到 template 选项时就能调用编译器
registerRuntimeCompiler(compileToFunction)这种设计的好处:
- Tree-shaking 友好:如果只用 render 函数(不用 template),编译器代码不会被打包
- 运行时可独立使用:
@vue/runtime-dom可以不依赖@vue/compiler-core - 可替换编译器:理论上可以注册不同的编译器实现
在组件中集成编译器:finishComponentSetup
当组件没有提供 render 函数但有 template 选项时,需要在组件初始化阶段编译模板:
ts
// 对标 packages/runtime-core/src/component.ts - finishComponentSetup
// 组件初始化的最后一步:确保组件有 render 函数
function finishComponentSetup(instance: any) {
const Component = instance.type // 获取组件选项对象
// 优先使用组件自身已有的 render 函数(可能来自 setup 返回或用户直接定义)
if (!instance.render) {
// 如果没有 render 函数,但注册了编译器且组件有 template 选项
if (compile && Component.template) {
const template = Component.template
// 编译 template 为 render 函数,并缓存到组件选项上
// 缓存的好处:同一组件多次实例化时不需要重复编译
Component.render = compile(template)
}
// 将 render 函数赋值给组件实例
instance.render = Component.render
}
}调用时机在组件 setup() 执行之后:
ts
// 对标 packages/runtime-core/src/component.ts - setupComponent
// 组件初始化入口:初始化 props、slots,然后执行 setup
function setupComponent(instance: any) {
const { props, children } = instance.vnode
initProps(instance, props) // 初始化组件的 props
initSlots(instance, children) // 初始化组件的 slots
// 执行有状态组件的 setup 逻辑
setupStatefulComponent(instance)
}
// 执行组件的 setup 函数
function setupStatefulComponent(instance: any) {
const Component = instance.type
const { setup } = Component
if (setup) {
// 调用 setup,传入 props 和上下文对象
const setupResult = setup(instance.props, {
emit: instance.emit,
})
// 处理 setup 的返回值
handleSetupResult(instance, setupResult)
} else {
// 没有 setup 函数,直接进入 finishComponentSetup
finishComponentSetup(instance)
}
}
// 处理 setup 函数的返回值
function handleSetupResult(instance: any, setupResult: any) {
if (typeof setupResult === 'function') {
// setup 返回函数 → 作为组件的 render 函数
instance.render = setupResult
} else if (typeof setupResult === 'object') {
// setup 返回对象 → 作为模板中可访问的数据
// proxyRefs 使得模板中使用 ref 时不需要 .value
instance.setupState = proxyRefs(setupResult)
}
// 最后完成组件设置(如果还没有 render 函数,会在这里编译 template)
finishComponentSetup(instance)
}完整示例:从模板到页面
使用 template 选项
ts
import { createApp, ref } from '@mini-vue/vue'
// 使用 template 选项定义组件
// 编译器会在运行时将 template 编译为 render 函数
const App = {
template: '<div>hi,{{ message }}</div>', // 模板字符串
setup() {
const message = ref('mini-vue') // 创建响应式数据
return {
message, // 暴露给模板使用
}
},
}
// 创建应用并挂载到 DOM
createApp(App).mount('#app')执行流程
1. createApp(App)
└── 创建应用实例
2. app.mount('#app')
└── createVNode(App) → 创建组件 VNode
└── render(vnode, container) → 开始渲染
└── patch(null, vnode) → 初次挂载
└── processComponent()
└── mountComponent()
├── createComponentInstance() → 创建组件实例
├── setupComponent()
│ ├── setup() 执行 → 返回 { message: ref('mini-vue') }
│ └── finishComponentSetup()
│ └── compile(template) → 编译模板
│ ├── baseParse() → 生成 AST
│ ├── transform() → 转换 AST
│ └── generate() → 生成代码字符串
│ └── new Function() → 得到 render 函数
│
└── setupRenderEffect()
└── render.call(proxy) → 执行 render 函数
└── createElementVNode('div', null, 'hi,' + toDisplayString(message))
└── VNode { type: 'div', children: 'hi,mini-vue' }
└── patch → mountElement → DOM最终 DOM
html
<div id="app">
<div>hi,mini-vue</div>
</div>使用 render 函数(无需编译器)
ts
import { createApp, ref, h } from '@mini-vue/runtime-dom'
// 使用 render 函数代替 template(无需编译器)
// setup 返回一个函数时,该函数即为组件的 render 函数
const App = {
setup() {
const message = ref('mini-vue')
// 直接返回 render 函数,使用 h() 创建 VNode
return () => h('div', null, 'hi,' + message.value)
},
}
createApp(App).mount('#app')这种方式跳过了编译步骤,直接使用 h 函数创建 VNode,适合不需要编译器的场景(如组件库开发)。
Vue 3 编译优化概览
Vue 3 的完整编译器还包含以下高级优化,我们在 mini-vue 中不实现,但了解它们对于理解 Vue 3 的性能优势至关重要:
PatchFlags
ts
// 对标 packages/shared/src/patchFlags.ts
// PatchFlags 使用位掩码(bit flags)表示节点的动态绑定类型
// 编译器在编译时分析模板,为每个 VNode 标记哪些部分是动态的
export const enum PatchFlags {
TEXT = 1, // 动态文本内容(如 {{ msg }})
CLASS = 1 << 1, // 动态 class 绑定(如 :class="active")
STYLE = 1 << 2, // 动态 style 绑定(如 :style="{ color }")
PROPS = 1 << 3, // 动态非 class/style 的属性(如 :id="dynamicId")
FULL_PROPS = 1 << 4, // 动态 key 属性(属性名也是动态的)
NEED_HYDRATION = 1 << 5, // SSR 水合标记
STABLE_FRAGMENT = 1 << 6, // 子节点顺序不变的 Fragment
KEYED_FRAGMENT = 1 << 7, // 带 key 的 Fragment
UNKEYED_FRAGMENT = 1 << 8, // 不带 key 的 Fragment
NEED_PATCH = 1 << 9, // 非 props 的补丁标记(如 ref、指令)
DYNAMIC_SLOTS = 1 << 10, // 动态插槽
HOISTED = -1, // 已提升的静态节点,diff 时可完全跳过
BAIL = -2, // 退出优化模式,进行完整 diff
}编译器在编译时分析每个节点的动态绑定,为 VNode 标记 PatchFlag。运行时 diff 时,只检查标记为动态的部分,跳过静态内容。
html
<div>
<span>静态内容</span>
<span>{{ dynamic }}</span>
</div>编译后的 {{ dynamic }} 对应的 VNode 会带上 PatchFlags.TEXT,diff 时只比较文本内容,不比较 props、children 等。
Block Tree
ts
// Block Tree 编译后的代码(示意)
// _openBlock 创建一个 Block 容器,收集其中的动态子节点
function render(_ctx) {
return (_openBlock(), _createElementBlock('div', null, [
_createElementVNode('span', null, '静态'), // 静态节点,不会进入 dynamicChildren
_createElementVNode('span', null, _toDisplayString(_ctx.msg), 1 /* TEXT */),
// 最后的 1 就是 PatchFlags.TEXT,标记此节点有动态文本
// 该节点会被自动收集到 Block 的 dynamicChildren 中
]))
}Block Tree 将模板中的动态节点"扁平化"收集到一个 dynamicChildren 数组中。运行时 diff 不再需要递归遍历整棵 VNode 树,只需遍历 dynamicChildren:
传统 diff:遍历整棵树 O(n)
Block diff:只遍历动态节点 O(dynamic nodes)静态提升(Static Hoisting)
ts
// 提升前:每次执行 render 都会重新创建静态 VNode 对象
function render(_ctx) {
return h('div', [
h('span', '静态内容'), // 每次 render 都创建新对象,浪费内存和 GC
h('span', _ctx.dynamic),
])
}
// 提升后:静态 VNode 提升到模块顶层,只创建一次
const _hoisted_1 = h('span', '静态内容') // 在模块加载时创建一次
function render(_ctx) {
return h('div', [
_hoisted_1, // 直接复用同一个对象引用,无需重新创建
h('span', _ctx.dynamic),
])
}静态提升减少了 VNode 的创建开销和 GC 压力。
缓存事件处理函数(Cache Event Handlers)
html
<button @click="handleClick">click</button>ts
// 未缓存:每次 render 都创建新的内联事件处理函数
function render(_ctx) {
return h('button', {
onClick: (...args) => _ctx.handleClick(...args) // 每次都是新引用 → 触发不必要的 props diff
})
}
// 缓存后:利用 _cache 数组复用同一个函数引用
function render(_ctx, _cache) {
return h('button', {
// 如果 _cache[0] 已存在则复用,否则创建并缓存
// 这样 diff 时 onClick 引用不变,不会触发 DOM 更新
onClick: _cache[0] || (_cache[0] = (...args) => _ctx.handleClick(...args))
})
}缓存避免了因事件处理函数引用变化导致的不必要 patch。
优化效果总结
| 优化手段 | 效果 | 节省的开销 |
|---|---|---|
| PatchFlags | 精确 diff | 跳过静态属性比较 |
| Block Tree | 扁平化 diff | 跳过静态子树遍历 |
| 静态提升 | 减少 VNode 创建 | GC 压力、内存分配 |
| 事件缓存 | 避免不必要 patch | props 比较、DOM 操作 |
这些优化的核心思想是:利用编译时的静态分析,减少运行时的工作量。
整个 mini-vue 架构总结
模块依赖图
┌─────────────────────────────────────────────────┐
│ vue │
│ (主入口:集成 compiler + runtime) │
│ ┌──────────────────────────────────────────┐ │
│ │ registerRuntimeCompiler │ │
│ │ compileToFunction │ │
│ └──────────────────────────────────────────┘ │
└──────────┬───────────────────┬──────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ compiler-core │ │ runtime-dom │
│ │ │ │
│ ┌─────────────┐│ │ ┌──────────────┐ │
│ │ parse.ts ││ │ │ nodeOps.ts │ │
│ │ transform.ts││ │ │ patchProp.ts │ │
│ │ codegen.ts ││ │ │ index.ts │ │
│ └─────────────┘│ │ └──────────────┘ │
└─────────────────┘ └───────┬──────────┘
│
▼
┌──────────────────┐
│ runtime-core │
│ │
│ ┌──────────────┐ │
│ │ renderer.ts │ │
│ │ component.ts │ │
│ │ vnode.ts │ │
│ │ scheduler.ts │ │
│ │ apiWatch.ts │ │
│ └──────────────┘ │
└───────┬──────────┘
│
▼
┌──────────────────┐
│ reactivity │
│ │
│ ┌──────────────┐ │
│ │ reactive.ts │ │
│ │ ref.ts │ │
│ │ computed.ts │ │
│ │ effect.ts │ │
│ │ watch.ts │ │
│ └──────────────┘ │
└──────────────────┘各模块职责
| 模块 | 职责 | 核心 API |
|---|---|---|
| reactivity | 响应式系统 | reactive / ref / computed / effect / watch |
| runtime-core | 平台无关运行时 | createRenderer / createVNode / h / createApp |
| runtime-dom | DOM 平台运行时 | render / createApp / nodeOps / patchProp |
| compiler-core | 编译器核心 | baseParse / transform / generate / baseCompile |
| vue | 主入口 | createApp(集成编译器和运行时) |
数据流向
用户代码
│
│ createApp({ template, setup })
▼
┌─────────────┐
│ setup() 执行 │ → 返回响应式数据
└──────┬──────┘
│
▼
┌──────────────┐
│ 编译 template │ → render 函数 (如果没有直接提供 render)
└──────┬───────┘
│
▼
┌──────────────────┐
│ render() 执行 │ → 生成 VNode 树
│ (依赖收集发生) │
└──────┬───────────┘
│
▼
┌──────────────────┐
│ patch(VNode, DOM) │ → 初次渲染 / 更新 DOM
└──────┬───────────┘
│
▼
Real DOM
│
│ 用户交互 → 修改响应式数据
▼
┌──────────────────┐
│ trigger → effect │ → 调度器批量执行
│ → 重新 render() │ → 新 VNode
│ → patch(新旧) │ → Diff → 最小化 DOM 更新
└──────────────────┘性能优化策略总结
编译时优化
| 策略 | 实现位置 | 说明 |
|---|---|---|
| PatchFlags | compiler-core (transform) | 标记动态绑定类型 |
| Block Tree | compiler-core (codegen) | 收集动态节点,扁平化 diff |
| 静态提升 | compiler-core (transform) | 静态 VNode 提升到模块顶层 |
| 事件缓存 | compiler-core (transform) | 缓存 inline 事件处理函数 |
| SSR 优化 | compiler-ssr | 生成字符串拼接代码 |
运行时优化
| 策略 | 实现位置 | 说明 |
|---|---|---|
| 响应式精确追踪 | reactivity (effect) | 只有依赖变化才触发更新 |
| 异步批量更新 | runtime-core (scheduler) | 合并同一 tick 内的多次更新 |
| 最长递增子序列 | runtime-core (renderer) | 最小化 DOM 移动操作 |
| 组件级更新 | runtime-core (renderer) | 只更新受影响的组件子树 |
| 位运算类型判断 | shared (ShapeFlags) | 高效的类型判断和组合 |
用户层优化
| 策略 | 说明 |
|---|---|
v-once | 只渲染一次,跳过后续 diff |
v-memo | 条件缓存子树 |
key 属性 | 帮助 diff 算法正确识别和复用节点 |
shallowRef / shallowReactive | 减少深层响应式追踪开销 |
computed | 缓存计算结果,避免重复计算 |
对比 React JSX 编译流程
Vue 3 模板编译
<div>{{ msg }}</div>
↓ Vue Compiler
const { toDisplayString, createElementVNode } = Vue
return function render(_ctx) {
return createElementVNode('div', null, toDisplayString(_ctx.msg), 1)
}React JSX 编译
jsx
<div>{msg}</div>
↓ Babel / SWC
import { jsx as _jsx } from 'react/jsx-runtime'
function Component() {
return _jsx('div', { children: msg })
}全链路对比
| 维度 | Vue 3 | React |
|---|---|---|
| 模板语法 | HTML-like Template | JSX(JavaScript 扩展) |
| 编译工具 | Vue Compiler(自研) | Babel / SWC / esbuild |
| 编译产物 | render 函数字符串 | JSX → jsx() 调用 |
| 运行时编译 | ✅ 支持(vue 完整版) | ❌ 不支持 |
| 构建时编译 | ✅ 推荐(@vue/compiler-sfc) | ✅ 必须 |
| 优化方式 | 编译时静态分析 + 运行时响应式 | React Compiler(实验性) |
| 更新粒度 | 组件级(响应式精确追踪) | 组件级(自顶向下 reconcile) |
| Diff 策略 | 双端 + LIS | 单向遍历 |
| 编译产物大小 | 带 PatchFlags,较大 | 较小(无优化标记) |
| 学习曲线 | 模板语法直观 | JSX 需了解 JS 表达式 |
核心理念差异
Vue 的编译时优化:通过编译器静态分析模板,在编译时就确定了哪些是动态的、哪些是静态的。运行时 diff 时只处理动态部分。
React 的运行时优化:传统上依赖 memo、useMemo、useCallback 等手动优化。React Compiler(2024+)正在尝试自动化编译时优化,但由于 JSX 的灵活性,优化空间不如 Vue 模板。
测试用例
ts
import { baseCompile } from '../src/compile'
describe('compile', () => {
// 测试:纯文本模板应正确编译
it('should compile text', () => {
const { code } = baseCompile('hello')
// 断言:生成的代码包含文本字面量和 render 函数
expect(code).toContain("'hello'")
expect(code).toContain('function render')
})
// 测试:插值表达式应正确编译
it('should compile interpolation', () => {
const { code } = baseCompile('{{ msg }}')
// 断言:包含 toDisplayString 辅助函数和变量名
expect(code).toContain('toDisplayString')
expect(code).toContain('msg')
})
// 测试:元素节点应正确编译
it('should compile element', () => {
const { code } = baseCompile('<div>hello</div>')
// 断言:包含 createElementVNode 调用和标签名
expect(code).toContain('createElementVNode')
expect(code).toContain("'div'")
})
// 测试:元素内包含插值的复杂模板应正确编译
it('should compile element with interpolation', () => {
const { code } = baseCompile('<div>hi,{{ msg }}</div>')
// 断言:同时包含元素创建、字符串转换、文本和 + 连接符
expect(code).toContain('createElementVNode')
expect(code).toContain('toDisplayString')
expect(code).toContain("'hi,'")
expect(code).toContain(' + ')
expect(code).toContain('msg')
})
// 测试:生成的代码应是合法的 JavaScript
it('should generate valid JavaScript', () => {
const { code } = baseCompile('<div>{{ message }}</div>')
// 断言:new Function 不会抛出语法错误
expect(() => new Function(code)).not.toThrow()
})
// 测试:生成的代码应能执行并返回正确的 VNode 结构
it('should generate executable render function', () => {
const { code } = baseCompile('<div>hello</div>')
// 模拟运行时辅助函数
const toDisplayString = (val: any) => String(val)
const createElementVNode = (tag: any, props: any, children: any) => ({
tag,
props,
children,
})
// 通过 new Function 创建工厂函数,传入模拟的运行时模块
const render = new Function('Vue', code)({
toDisplayString,
createElementVNode,
})
// 执行 render 函数并验证返回的 VNode
const vnode = render({ message: 'world' })
expect(vnode.tag).toBe('div')
expect(vnode.children).toBe('hello')
})
// 测试:动态数据应正确反映在 render 结果中
it('should compile and execute with dynamic data', () => {
const { code } = baseCompile('<div>{{ message }}</div>')
// 模拟运行时辅助函数
const toDisplayString = (val: any) => String(val)
const createElementVNode = (tag: any, props: any, children: any) => ({
tag,
props,
children,
})
const render = new Function('Vue', code)({
toDisplayString,
createElementVNode,
})
// 传入不同的数据,验证 render 输出随数据变化
const vnode = render({ message: 'hello world' })
expect(vnode.tag).toBe('div')
expect(vnode.children).toBe('hello world')
})
})
describe('full integration', () => {
// 端到端测试:模板 → 编译 → render → VNode 的完整流程
it('should work end to end: template → compile → render → vnode', () => {
const template = '<div>hi,{{ name }}</div>'
const { code } = baseCompile(template)
// 模拟运行时辅助函数
const helpers = {
toDisplayString: (val: any) => String(val ?? ''),
createElementVNode: (type: any, props: any, children: any) => ({
type,
props,
children,
}),
}
// 编译并执行 render 函数
const render = new Function('Vue', code)(helpers)
const ctx = { name: 'mini-vue' } // 模拟组件上下文
const vnode = render(ctx)
// 断言:VNode 的结构和内容正确
expect(vnode.type).toBe('div')
expect(vnode.children).toBe('hi,mini-vue')
})
// 测试:重新渲染时应反映数据变化
it('should reflect data changes on re-render', () => {
const template = '<p>count: {{ count }}</p>'
const { code } = baseCompile(template)
const helpers = {
toDisplayString: (val: any) => String(val ?? ''),
createElementVNode: (type: any, props: any, children: any) => ({
type,
props,
children,
}),
}
const render = new Function('Vue', code)(helpers)
// 第一次渲染:count = 0
const vnode1 = render({ count: 0 })
expect(vnode1.children).toBe('count: 0')
// 第二次渲染:count = 42,验证数据变化正确反映
const vnode2 = render({ count: 42 })
expect(vnode2.children).toBe('count: 42')
})
})本节小结
- baseCompile — 编译器核心入口,串联 parse → transform → codegen 三个阶段
- compileToFunction — 通过
new Function()将代码字符串转为可执行的 render 函数 - registerRuntimeCompiler — 注册模式,运行时与编译器解耦
- finishComponentSetup — 组件初始化时编译 template,获取 render 函数
- 编译优化 — PatchFlags / Block Tree / 静态提升 / 事件缓存(Vue 3 完整版)
- 模块架构 — reactivity → runtime-core → runtime-dom → vue,层次清晰、职责明确
全系列总结
至此,我们完成了 mini-vue 的全部实现。回顾整个系列:
响应式系统(第 1-7 节)
从 Proxy 代理到 effect 依赖收集,从 ref/computed 到 watch/watchEffect,构建了 Vue 3 的响应式核心。核心思想:通过自动追踪依赖关系,实现数据变化时精确触发更新。
运行时系统(第 8-12 节)
从 VNode 数据结构到 renderer 的 mount/update 流程,从双端 Diff + LIS 算法到跨平台 createRenderer,构建了 Vue 3 的渲染核心。核心思想:声明式的 VNode 描述 UI,命令式的 renderer 操作 DOM。
编译系统(第 21-24 节)
从 parse 递归下降解析,到 transform 插件化转换,到 codegen 代码生成,再到与运行时的联调。核心思想:将人类友好的模板语法,编译为机器高效的 render 函数。
贯穿全局的设计原则
| 原则 | 体现 |
|---|---|
| 关注点分离 | 响应式 / 运行时 / 编译器 三大系统独立 |
| 依赖倒置 | runtime-core 不依赖 runtime-dom,通过 options 注入 |
| 开闭原则 | transform 插件化,可扩展不可修改 |
| 单一职责 | parse 只管解析,transform 只管转换,codegen 只管生成 |
| 编译时优化 | 尽可能在编译时完成分析,减少运行时工作量 |
| 渐进式架构 | 按需使用:只用响应式 / 运行时 / 完整版 |
Vue 3 = Reactivity + Runtime + Compiler
= 精确的变化追踪 + 高效的 DOM 操作 + 智能的编译优化