《Vue3 从入门到大神25篇》手撕 Vue3 响应式原理 —— 手写 Mini-Vue
前言
前面的文章中,我们已经反复提到:
Vue3 的响应式基于Proxy + Reflect。
你也知道了:
reactive创建响应式对象effect是副作用函数track收集依赖trigger触发更新
但问题是:
这些东西在源码里到底是怎么串起来的?
这一篇,我们从零手写一个Mini-Vue 响应式系统,只用一个文件、不到 100 行代码,就把 Vue3 响应式的核心链路跑通。
写完之后你会发现:
原来 Vue3 的响应式并没有想象中那么神秘。
一、目标:我们要实现什么?
最终效果:
const state = reactive({ count: 0 }) effect(() => { console.log('count:', state.count) }) state.count++ // 控制台自动打印最新值✅ 数据变化 → 自动重新执行副作用函数
这就是 Vue3 响应式的最小可用模型。
二、核心设计思路
┌─────────────────────────────────────────────┐ │ 响应式系统 │ ├─────────────────────────────────────────────┤ │ │ │ reactive(obj) ──→ Proxy 代理对象 │ │ │ │ │ ▼ │ │ 读取属性 (get) ──→ track() 收集依赖 │ │ │ │ │ ▼ │ │ 修改属性 (set) ──→ trigger() 触发更新 │ │ │ │ │ ▼ │ │ effect() 重新执行 │ │ │ └─────────────────────────────────────────────┘三、数据结构设计
1️⃣ 依赖存储结构
WeakMap (target → Map (key → Set (effects)))// 存储所有依赖关系 const targetMap = new WeakMap()为什么用这三层?
层级 | 类型 | 作用 |
|---|---|---|
WeakMap | target → ... | 每个响应式对象一个 Map |
Map | key → ... | 每个属性一个 Set |
Set | effects | 该属性的所有副作用函数 |
📌WeakMap 的好处:
当响应式对象被垃圾回收时,依赖关系自动清除,不会内存泄漏。
四、实现 track(依赖收集)
// 当前正在执行的 effect let activeEffect = null function track(target, key) { if (!activeEffect) return // 获取 target 对应的 Map let depsMap = targetMap.get(target) if (!depsMap) { depsMap = new Map() targetMap.set(target, depsMap) } // 获取 key 对应的 Set let dep = depsMap.get(key) if (!dep) { dep = new Set() depsMap.set(key, dep) } // 将当前 effect 加入 Set dep.add(activeEffect) }📌核心逻辑:
把"当前正在运行的副作用函数"和"被读取的属性"建立关联。
五、实现 trigger(触发更新)
function trigger(target, key) { const depsMap = targetMap.get(target) if (!depsMap) return const effects = depsMap.get(key) if (!effects) return // 依次执行所有副作用函数 effects.forEach(effect => effect()) }📌核心逻辑:
找到和这个属性关联的所有副作用函数,全部重新执行。
六、实现 reactive(响应式对象)
function reactive(obj) { return new Proxy(obj, { get(target, key, receiver) { // 依赖收集 track(target, key) // 返回属性值 return Reflect.get(target, key, receiver) }, set(target, key, value, receiver) { // 设置属性值 const result = Reflect.set(target, key, value, receiver) // 触发更新 trigger(target, key) return result } }) }📌这就是 Vue3 reactive 的最小实现:
get 中收集依赖
set 中触发更新
Reflect 保证正确的 this 指向
七、实现 effect(副作用函数)
function effect(fn) { // 包装成可调度函数 const effectFn = () => { activeEffect = effectFn fn() activeEffect = null } // 立即执行一次 effectFn() return effectFn }📌关键点:
把当前 effect 设为
activeEffect执行用户传入的函数(此时会触发 get → track)
执行完毕后清空
activeEffect
八、完整代码(不到 60 行)
// ===== Mini-Vue 响应式核心 ===== const targetMap = new WeakMap() let activeEffect = null function track(target, key) { if (!activeEffect) return let depsMap = targetMap.get(target) if (!depsMap) { depsMap = new Map() targetMap.set(target, depsMap) } let dep = depsMap.get(key) if (!dep) { dep = new Set() depsMap.set(key, dep) } dep.add(activeEffect) } function trigger(target, key) { const depsMap = targetMap.get(target) if (!depsMap) return const effects = depsMap.get(key) effects?.forEach(fn => fn()) } function reactive(obj) { return new Proxy(obj, { get(target, key, receiver) { track(target, key) return Reflect.get(target, key, receiver) }, set(target, key, value, receiver) { const result = Reflect.set(target, key, value, receiver) trigger(target, key) return result } }) } function effect(fn) { const effectFn = () => { activeEffect = effectFn fn() activeEffect = null } effectFn() return effectFn }九、验证:跑一下试试
const state = reactive({ count: 0, name: 'Vue3' }) effect(() => { console.log(`count is: ${state.count}`) }) // 输出: count is: 0 state.count++ // 输出: count is: 1 state.count = 10 // 输出: count is: 10✅完美运行!
十、对照 Vue3 源码
我们的实现 | Vue3 源码 |
|---|---|
|
|
| 同文件中的 |
|
|
|
|
📌Vue3 源码更复杂(处理了数组、Map/Set、嵌套对象等),但核心思想和我们写的一模一样。
十一、局限性说明(诚实交代)
我们的 Mini-Vue 还不能处理:
场景 | 说明 |
|---|---|
嵌套对象 | 需要递归代理(Vue3 用 |
数组 | 需要特殊处理 |
Map / Set | 需要重写方法 |
多次触发 | 需要 effect 栈防止嵌套 |
调度执行 | 需要 scheduler 控制执行时机 |
这些会在后续文章中逐步完善。
十二、面试高频问答
Q1:手写 Vue3 响应式的核心步骤?
用 WeakMap 存储依赖关系
Proxy get 中 track 收集依赖
Proxy set 中 trigger 触发更新
effect 包装函数并立即执行
Q2:为什么用 WeakMap 而不是 Map?
WeakMap 的键是弱引用,不影响垃圾回收,防止内存泄漏。
Q3:Reflect 在这里的作用?
保证正确的 this 指向,统一操作 API。
十三、总结(源码级)
Vue3 响应式的本质是Proxy + 依赖收集 + 触发更新
核心数据结构是WeakMap → Map → Set
track建立关联,trigger触发执行手写一遍,比看十遍源码都有用
📢 下期预告
👉第 26 篇:Virtual DOM 与 Diff 算法 —— Vue3 的高效更新机制
