《Vue3 从入门到大神25篇》手撕 Vue3 响应式原理 —— 手写 Mini-Vue

📅 2026/7/8 13:33:03 👁️ 阅读次数 📝 编程学习
《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 }

📌关键点

  1. 把当前 effect 设为activeEffect

  2. 执行用户传入的函数(此时会触发 get → track)

  3. 执行完毕后清空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 源码

track()

packages/reactivity/src/effect.ts中的track

trigger()

同文件中的trigger

reactive()

packages/reactivity/src/reactive.ts

effect()

packages/reactivity/src/effect.ts

📌Vue3 源码更复杂(处理了数组、Map/Set、嵌套对象等),但核心思想和我们写的一模一样


十一、局限性说明(诚实交代)

我们的 Mini-Vue 还不能处理:

场景

说明

嵌套对象

需要递归代理(Vue3 用Reflect.get+ 懒代理)

数组

需要特殊处理length和索引

Map / Set

需要重写方法

多次触发

需要 effect 栈防止嵌套

调度执行

需要 scheduler 控制执行时机

这些会在后续文章中逐步完善。


十二、面试高频问答

Q1:手写 Vue3 响应式的核心步骤?

  1. 用 WeakMap 存储依赖关系

  2. Proxy get 中 track 收集依赖

  3. Proxy set 中 trigger 触发更新

  4. effect 包装函数并立即执行

Q2:为什么用 WeakMap 而不是 Map?

WeakMap 的键是弱引用,不影响垃圾回收,防止内存泄漏。

Q3:Reflect 在这里的作用?

保证正确的 this 指向,统一操作 API。


十三、总结(源码级)

  • Vue3 响应式的本质是Proxy + 依赖收集 + 触发更新

  • 核心数据结构是WeakMap → Map → Set

  • track建立关联,trigger触发执行

  • 手写一遍,比看十遍源码都有用


📢 下期预告

👉第 26 篇:Virtual DOM 与 Diff 算法 —— Vue3 的高效更新机制