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 次
