《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 的高效更新机制

相关推荐
Blanche1500几秒前
Webpack 构建流程解析
javascript·面试
粥里有勺糖1 分钟前
体验一下最近比较🔥的闪卡SKILL
前端·github
flash俊杰1 分钟前
WebGL 实时合成管线:纹理、Shader 滤镜链与离屏渲染调度
前端
前端snow1 分钟前
ai agent --- LangSmith 全链路观测
前端
Csvn2 分钟前
Hooks 原理:把"每次重跑的函数"变成"有记忆的组件"
前端
计算机魔术师2 分钟前
用AI拒批老人看病?美国这个试点项目的激励机制出了大问题
前端
风骏时光牛马2 分钟前
AI服务线上响应异常故障
前端
IT_陈寒2 分钟前
JavaScript的this指向问题又让我加了个班
前端·人工智能·后端
PC2005_cloud2 分钟前
Nginx 学习笔记:Server 块配置详解,域名路由与多站点部署实战
前端·后端
YIAN2 分钟前
LangChain.js 对话记忆体系(一):内存存储与文件持久化,让 AI 拥有对话记忆
前端·后端·langchain