Vue3响应式系统实现
约 1657 字大约 6 分钟
vue3reactivityproxy
2025-07-26
概述
Vue 3 使用 ES6 Proxy 重新实现了响应式系统,相比 Vue 2 的 Object.defineProperty 方案,能够拦截更多操作类型(如属性新增、删除、数组索引修改等),性能更优,且支持更灵活的响应式数据结构。
响应式系统总览
1. Proxy 基础:reactive 实现
// reactive 的简化实现
const reactiveMap = new WeakMap();
function reactive(target) {
if (reactiveMap.has(target)) {
return reactiveMap.get(target); // 避免重复代理
}
const proxy = new Proxy(target, {
get(target, key, receiver) {
track(target, key); // 收集依赖
const result = Reflect.get(target, key, receiver);
// 深度响应式:返回的对象也进行代理
if (typeof result === 'object' && result !== null) {
return reactive(result);
}
return result;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
trigger(target, key); // 触发更新
}
return result;
},
deleteProperty(target, key) {
const hadKey = key in target;
const result = Reflect.deleteProperty(target, key);
if (hadKey) {
trigger(target, key); // 删除属性也触发更新
}
return result;
},
has(target, key) {
track(target, key);
return Reflect.has(target, key);
},
ownKeys(target) {
track(target, Symbol('iterate'));
return Reflect.ownKeys(target);
}
});
reactiveMap.set(target, proxy);
return proxy;
}2. ref 的实现
ref 用于包装基本类型值,因为 Proxy 只能代理对象。
// ref 的简化实现
function ref(value) {
return new RefImpl(value);
}
class RefImpl {
constructor(value) {
this._rawValue = value;
this._value = isObject(value) ? reactive(value) : value;
}
get value() {
track(this, 'value'); // 收集依赖
return this._value;
}
set value(newValue) {
if (newValue !== this._rawValue) {
this._rawValue = newValue;
this._value = isObject(newValue) ? reactive(newValue) : newValue;
trigger(this, 'value'); // 触发更新
}
}
}
// 使用
const count = ref(0);
console.log(count.value); // 0
count.value++; // 触发更新3. Effect、Track 与 Trigger
依赖关系数据结构
// 依赖收集核心
const targetMap = new WeakMap();
let activeEffect = null;
const effectStack = [];
function effect(fn) {
const effectFn = () => {
cleanup(effectFn); // 清除旧依赖
activeEffect = effectFn;
effectStack.push(effectFn);
const result = fn(); // 执行函数,触发 getter,收集依赖
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
return result;
};
effectFn.deps = []; // 存储依赖集合的引用
effectFn(); // 首次立即执行
return effectFn;
}
function cleanup(effectFn) {
for (const dep of effectFn.deps) {
dep.delete(effectFn); // 从所有依赖集合中移除自身
}
effectFn.deps.length = 0;
}track 收集依赖
function track(target, key) {
if (!activeEffect) return; // 没有正在执行的 effect,无需收集
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()));
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
activeEffect.deps.push(dep); // 双向关联
}
}trigger 触发更新
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (!dep) return;
// 创建新 Set 避免无限循环
const effectsToRun = new Set();
dep.forEach(effectFn => {
// 避免在 effect 中修改依赖导致死循环
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn);
}
});
effectsToRun.forEach(effectFn => {
if (effectFn.scheduler) {
effectFn.scheduler(effectFn); // 调度器(如 computed 的延迟执行)
} else {
effectFn(); // 直接执行
}
});
}4. computed 的实现
function computed(getter) {
let value;
let dirty = true; // 标记是否需要重新计算
const effectFn = effect(getter, {
lazy: true,
scheduler() {
if (!dirty) {
dirty = true;
trigger(obj, 'value'); // 通知依赖 computed 的 effect
}
}
});
const obj = {
get value() {
if (dirty) {
value = effectFn(); // 惰性计算
dirty = false;
}
track(obj, 'value'); // computed 也需要被依赖收集
return value;
}
};
return obj;
}
// 使用
const state = reactive({ price: 10, quantity: 3 });
const total = computed(() => state.price * state.quantity);
console.log(total.value); // 30(首次计算)
console.log(total.value); // 30(缓存,不重新计算)
state.price = 20;
console.log(total.value); // 60(dirty = true,重新计算)5. 依赖关系图可视化
6. shallowReactive 与 readonly
// shallowReactive: 只有第一层是响应式的
const state = shallowReactive({
user: {
name: 'Alice', // 修改 state.user 触发更新
address: {
city: 'Beijing' // 修改 state.user.address.city 不触发更新
}
}
});
// readonly: 只读的响应式对象
const original = reactive({ count: 0 });
const readonlyState = readonly(original);
readonlyState.count++; // 警告:Set operation on key "count" failed: target is readonly
// shallowReadonly: 只有第一层只读
const shallowRO = shallowReadonly({ nested: { value: 1 } });
shallowRO.nested = {}; // 警告
shallowRO.nested.value = 2; // 允许(深层不受限)7. toRaw 与 markRaw
const obj = { name: 'test' };
const proxy = reactive(obj);
// toRaw: 获取原始对象
const raw = toRaw(proxy);
console.log(raw === obj); // true
// 修改 raw 不会触发更新
// markRaw: 标记对象永远不被转为响应式
const heavyLib = markRaw({
// 大型第三方库对象,不需要响应式
process(data) { /* ... */ }
});
const state = reactive({
lib: heavyLib // 即使放入 reactive,heavyLib 也不会被代理
});
console.log(isReactive(state.lib)); // false8. 与 Vue 2 Object.defineProperty 的对比
// Vue 2 的限制
const vm = new Vue({
data: { obj: { a: 1 } }
});
vm.obj.b = 2; // 不触发更新!
Vue.set(vm.obj, 'b', 2); // 必须使用 Vue.set
vm.arr[0] = 'new'; // 不触发更新!
Vue.set(vm.arr, 0, 'new'); // 必须使用 Vue.set
// Vue 3 无此限制
const state = reactive({ obj: { a: 1 }, arr: ['old'] });
state.obj.b = 2; // 自动触发更新
state.arr[0] = 'new'; // 自动触发更新
delete state.obj.a; // 自动触发更新9. 响应式 API 选择指南
| 场景 | API | 说明 |
|---|---|---|
| 对象/数组 | reactive() | 深度响应式,返回 Proxy |
| 基本类型 | ref() | 包装为 { value } 对象 |
| 计算值 | computed() | 惰性求值,自动缓存 |
| 大型只读数据 | shallowReadonly() | 减少代理开销 |
| 第三方库对象 | markRaw() | 跳过响应式转换 |
| 性能敏感列表 | shallowReactive() | 只响应第一层变化 |
| 获取原始对象 | toRaw() | 绕过响应式进行操作 |
总结
Vue 3 的响应式系统基于 Proxy 实现了自动依赖收集和精确的更新触发。核心流程是:reactive/ref 创建代理对象,effect 注册副作用函数,属性读取时 track 收集依赖,属性修改时 trigger 通知更新。computed 在此基础上实现了惰性求值和缓存。相比 Vue 2 的 Object.defineProperty,Proxy 方案能拦截更多操作类型,支持惰性递归代理,性能和灵活性都有显著提升。
贡献者
更新日志
2026/3/14 13:09
查看所有更新日志
9f6c2-feat: organize wiki content and refresh site setup于