Vue 3 响应式原理实战:用 Proxy 手写迷你 reactive 理解依赖收集

test12026-10-200 次阅读

为什么值得手写一遍

平时用 ref 和 reactive 很顺手,但遇到"改了值视图不更新"时,光看文档很难定位。Vue 3 的响应式核心是 Proxy 加依赖收集,把它简化成几十行自己写一遍,很多坑就自然想通了。

核心三件套

用一个 WeakMap 串起"对象到字段到副作用"的关系:targetMap 以原对象为键,存 depsMap;depsMap 以字段名为键,存 dep;dep 是一个保存所有副作用函数的 Set。

let activeEffect = null;
const targetMap = new WeakMap();

function track(target, key) {
  if (!activeEffect) return;
  let depsMap = targetMap.get(target);
  if (!depsMap) targetMap.set(target, (depsMap = new Map()));
  let dep = depsMap.get(key);
  if (!dep) depsMap.set(key, (dep = new Set()));
  dep.add(activeEffect);
}

function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  const dep = depsMap.get(key);
  if (dep) dep.forEach((effect) => effect());
}

用 Proxy 实现 reactive

get 时做 track 收集依赖,set 时做 trigger 触发更新;遇到嵌套对象递归包一层 reactive,保证深层属性也响应式。

function reactive(target) {
  return new Proxy(target, {
    get(obj, key, receiver) {
      const res = Reflect.get(obj, key, receiver);
      track(obj, key);
      return typeof res === "object" && res !== null ? reactive(res) : res;
    },
    set(obj, key, value, receiver) {
      const res = Reflect.set(obj, key, value, receiver);
      trigger(obj, key);
      return res;
    },
  });
}

function effect(fn) {
  activeEffect = fn;
  fn();
  activeEffect = null;
}

const state = reactive({ count: 0 });
effect(() => console.log("count =", state.count));
state.count++; // 自动打印 count = 1

和真实实现的差异

上面是原理骨架。Vue 真实版本还多了三件事:用 effect 栈支持嵌套依赖、用调度器把同一轮的多次 set 合并成一次更新(避免抖动)、以及在 set 前后做浅/深比较来决定要不要真的触发。理解这几层,就知道为什么 Vue 默认是"异步批量更新"。

一个常见坑:解构丢掉响应

const { count } = reactiveState 之后,count 只是个普通数字,再也不经过 Proxy 的 get/set 拦截,改了视图当然不动。这也是官方推荐用 toRefs 或 ref 单独导出、而不是直接解构的原因。

小结

  • 响应式 = Proxy 拦截读写 + 依赖收集 + 触发更新。
  • track 在 get 时记录"谁在用这个字段",trigger 在 set 时通知它们。
  • 嵌套对象要递归 reactive,否则深层改动失响应。
  • 解构会脱离 Proxy,需用 toRefs 保住响应。
T

test1

文章作者

为什么值得手写一遍 平时用 ref 和 reactive 很顺手,但遇到"改了值视图不更新"时,光看文档很难定位。Vu...

分类
技术
发布时间
2026-10-20
字数
约 1827 字
阅读
0 次