精通 Vue 3 的核心原理,关键在于理解其"编译时与运行时协同 "的设计哲学。你需要能清晰地回答:Proxy 如何实现更彻底的响应式?编译阶段做了什么让运行时 Diff 从 O(n) 趋近于 O(1)?Composition API 的 setup 在初始化流程中扮演什么角色?
一句话判断标准:
给你一个 Vue 3 组件,你能在脑中推演出从"模板编译"到"
Proxy拦截",再到"Block Tree精准更新"的完整链路,并解释每个环节相比 Vue 2 的优化所在。
下面按四大核心机制 + 源码学习路径展开。
⚙️ 模块一:响应式系统------从 Object.defineProperty 到 Proxy
这是 Vue 3 一切变化的起点,核心模块在 packages/reactivity,可以完全脱离 Vue 框架独立使用。
1. 核心三件套:reactive、effect、track/trigger Vue 3 用 Proxy 替代了 Object.defineProperty,从根本上解决了 Vue 2 无法监听属性新增/删除、数组索引修改等局限。
reactive:将普通对象包装为Proxy,在get时调用track收集依赖,在set时调用trigger派发更新。effect:代表一个"副作用函数"(如组件的渲染函数)。当它执行并访问响应式数据时,track会将这个effect收集到对应属性的依赖集合中。- 依赖管理 :依赖关系不再像 Vue 2 那样通过闭包中的
Dep维护,而是集中在effect.ts中,通过targetMap(WeakMap嵌套Map的结构)进行管理,支持更细粒度的追踪。
2. 必须理解的对比与边界
refvsreactive:ref通过.value访问,内部对对象值也使用reactive包装,但对原始值(string/number)的响应式是靠一个内部的RefImpl类实现的,而非Proxy。Proxy的边界 :Proxy代理的是整个对象,因此对新增/删除属性、数组索引赋值、length修改都能完美监听。但对于Map/Set/WeakMap等集合类型,需要专门的集合处理器(collectionHandlers)来拦截get/set/add/delete等操作。
学到什么程度 :能手写一个基于 Proxy + effect 的迷你响应式模型,并解释 track 和 trigger 在 get/set 陷阱中分别做了什么。
🛠️ 模块二:模板编译------从"全量 Diff"到"靶向更新"
这是 Vue 3 性能提升的关键引擎 。编译器的核心工作是将模板转化为一个带有优化提示 的 render 函数。
1. 核心优化:PatchFlags 与 Block Tree
PatchFlags(补丁标记) :编译器静态分析模板,为每个动态节点打上数字枚举标记。例如,1代表TEXT(文本内容动态),2代表CLASS(class 动态)。运行时 Diff 时,只检查标记所指示的那一项,而非全量对比所有属性。Block Tree(块树) :编译器将动态节点 收集到一个扁平化的数组中,挂载在Block的dynamicChildren属性上。运行时更新时,直接遍历这个动态节点数组进行 Diff,完全跳过所有静态节点 。这使得 Diff 的复杂度从与总节点数 相关,降为只与动态节点数相关,实现了数量级的性能飞跃。
2. 静态提升 编译器将纯静态的 VNode 提取到 render 函数外部,只创建一次,后续渲染直接复用,避免了每次渲染都重复创建 VNode 的开销。
学到什么程度 :能手写一个简化版的 transform,将一段包含静态和动态节点的模板解析为带有 PatchFlags 的 AST,并解释 Block 是如何收集 dynamicChildren 的。
🌳 模块三:虚拟 DOM 与 Diff------LIS 算法的最小化移动
Vue 3 的 Diff 算法在 packages/runtime-core/src/renderer.ts 的 patchKeyedChildren 中实现,核心是快速 Diff + 最长递增子序列(LIS) 。
1. Diff 的三个核心阶段
- 同步前置/后置节点:先从头尾两端对比,处理掉相同的前缀和后缀节点,这是对 Vue 2 双端对比的继承。
- 处理新增/删除:如果旧节点先遍历完,说明有新节点需要挂载;如果新节点先遍历完,说明有旧节点需要卸载。
- 处理中间乱序节点(核心) :
- 构建
keyToNewIndexMap,记录新节点key到索引的映射。 - 遍历旧节点,找出可复用的节点,并记录其在新列表中的位置,生成一个新旧索引的映射数组
newIndexToOldIndexMap。 - 对这个映射数组求 最长递增子序列(LIS) 。LIS 中的节点不需要移动 ,只需移动不在 LIS 中的节点,从而保证 DOM 移动操作次数最少。
- 构建
2. LIS 的实现细节 getSequence 函数采用了 贪心 + 二分查找 + 前驱节点回溯 的策略,将时间复杂度控制在 O(N log N),这是 Diff 高效处理大规模列表的关键。
学到什么程度 :能手写 getSequence 函数,并解释在 Diff 中,为什么根据 LIS 逆序移动节点可以保证 DOM 操作的稳定性与最小化。
🧩 模块四:组件初始化与 Composition API------setup 的枢纽角色
Vue 3 的初始化流程围绕 setup 函数重构,使其成为组件逻辑的唯一入口。
1. setup 的执行链路 组件初始化始于 createComponentInstance,随后在 setupStatefulComponent 中调用 setup。关键步骤包括:
- 暂停依赖追踪 :调用
pauseTracking(),避免setup内部的普通变量访问被误收集为响应式依赖。 - 参数注入 :
props被shallowReadonly包装为浅层只读,防止子组件直接修改;context则提供attrs、slots、emit等非响应式工具。 - 结果处理 :
setup返回的对象会与props等一起,成为模板渲染时可访问的上下文。
2. 生命周期的注册机制 onMounted、onUpdated 等 Composition API 钩子,本质是通过 injectHook 方法,将回调函数注册到当前组件实例对应的生命周期数组中。当组件运行到相应阶段时,再遍历执行这些数组中的回调。
学到什么程度 :能画出从 createApp 到 setup 执行,再到 onMounted 触发的完整时序;能解释 setup 中 pauseTracking 的设计意图。
🧪 源码学习路径:读、调、写、画
1. 学习顺序建议(6-8 周)
- 第 1-2 周(地基) :
packages/reactivity。聚焦reactive.ts、effect.ts、ref.ts,手写迷你响应式。 - 第 3 周(引擎) :
packages/runtime-core/src/renderer.ts。聚焦patchKeyedChildren和getSequence,手写 LIS 和简化 Diff。 - 第 4 周(优化) :
packages/compiler-core。聚焦transform和codegen,理解PatchFlags和Block Tree的生成过程。 - 第 5 周(枢纽) :
packages/runtime-core/src/component.ts。聚焦setupStatefulComponent和injectHook,理清初始化与生命周期链路。 - 第 6-8 周(综合) :参考
mini-vue项目,手写一个包含响应式、编译器、运行时三大核心的微型 Vue 3,或用交互式架构书(如 GitHub 上的 14 章 Vue 3 Core 架构书)辅助源码阅读。
2. 工具与方法
- 模板编译探索器 :使用
vue-next-template-explorer在线查看模板编译后的render函数,直观理解PatchFlags和Block的生成结果。 - 调试优先 :在
packages/reactivity/src/effect.ts的track和trigger中打断点,观察一次ref赋值引发的完整依赖收集与派发更新调用栈。
💎 验证自己"精通"了的标志
能全部做到,说明 Vue 3 核心原理已经扎实:
- 能解释
Proxy相比Object.defineProperty,在哪些具体场景下解决了响应式盲区。 - 能画出
track和trigger在Proxy的get/set陷阱中协作的依赖管理图。 - 能说出
PatchFlags的至少三个枚举值及其含义,并解释Block Tree如何将 Diff 复杂度与动态节点数关联。 - 能手写
getSequence(LIS),并解释它在 Diff 中如何确定"不移动"的节点集合。 - 能完整描述
setup函数的执行链路,包括pauseTracking的作用和props被shallowReadonly包装的原因。 - 能解释
onMounted在源码层面是如何被注册和触发的。 - 能手写一个不超过 300 行、包含
Proxy响应式与PatchFlags概念演示的 Mini-Vue3 模型。
Vue 3 的核心原理是一套编译时与运行时深度协同、以最小代价实现精准更新 的体系。精通它,是为了在遇到"响应式失效""列表更新性能瓶颈""Composition API 逻辑组织困惑"时,能直接定位到 Proxy 陷阱、PatchFlags 生成或 effect 调度的层面,而非停留在"换个写法试试"的经验层面。