Vue 3 响应式原理
从基础到实现,理解 Vue 3 响应式系统的设计思路
问题
Vue 3 如何实现响应式系统,使数据变化时自动更新相关依赖?
解答
单个值的响应式
普通 JavaScript 代码中,变量变化不会自动更新相关计算结果:
let price = 10, quantity = 2;
const total = price * quantity;
console.log(`total: ${total}`); // total: 20
price = 20;
console.log(`total: ${total}`); // total: 20(未更新)
通过收集副作用(effect)和触发更新,可以实现响应式:
let price = 10, quantity = 2, total = 0;
const dep = new Set();
const effect = () => { total = price * quantity };
const track = () => { dep.add(effect) };
const trigger = () => { dep.forEach(effect => effect()) };
track();
trigger();
console.log(`total: ${total}`); // total: 20
price = 20;
trigger();
console.log(`total: ${total}`); // total: 40
单个对象的响应式
使用 Map 存储对象每个属性的依赖:
let product = { price: 10, quantity: 2 }, total = 0;
const depsMap = new Map();
const effect = () => { total = product.price * product.quantity };
const track = key => {
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(effect);
}
const trigger = key => {
let dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => effect());
}
};
track('price');
track('quantity');
trigger('price');
console.log(`total: ${total}`); // total: 20
多个对象的响应式
使用 WeakMap 管理多个响应式对象:
const targetMap = new WeakMap();
const track = (target, key) => {
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(effect);
}
const trigger = (target, key) => {
const depsMap = targetMap.get(target);
if (!depsMap) return;
let dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => effect());
}
};
使用 Proxy 和 Reflect 自动追踪
通过 Proxy 拦截对象的读取和修改操作,自动执行 track 和 trigger:
function reactive(target) {
const handler = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
track(target, key);
return result;
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, key);
return result;
}
};
return new Proxy(target, handler);
}
let product = reactive({ price: 10, quantity: 2 });
let total = 0;
effect(() => {
total = product.price * product.quantity;
});
console.log(total); // 20
product.price = 20;
console.log(total); // 40(自动更新)
实现 activeEffect
引入 activeEffect 变量,自动收集当前执行的副作用:
const targetMap = new WeakMap();
let activeEffect = null;
const effect = eff => {
activeEffect = eff;
activeEffect();
activeEffect = null;
}
const track = (target, key) => {
if (activeEffect) {
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);
}
}
// 使用方式
let product = reactive({ price: 10, quantity: 2 });
let total = 0;
effect(() => {
total = product.price * product.quantity;
});
实现 ref
ref 用于将基本类型值转换为响应式对象:
const ref = raw => {
const r = {
get value() {
track(r, 'value');
return raw;
},
set value(newVal) {
raw = newVal;
trigger(r, 'value');
}
};
return r;
}
// 使用方式
let salePrice = ref(0);
let product = reactive({ price: 10, quantity: 2 });
effect(() => {
salePrice.value = product.price * 0.9;
});
console.log(salePrice.value); // 9
product.price = 20;
console.log(salePrice.value); // 18
实现 computed
computed 基于 ref 和 effect 实现:
const computed = getter => {
let result = ref();
effect(() => result.value = getter());
return result;
}
// 使用方式
let product = reactive({ price: 10, quantity: 2 });
let salePrice = computed(() => product.price * 0.9);
let total = computed(() => salePrice.value * product.quantity);
console.log(total.value); // 18
product.price = 20;
console.log(total.value); // 36
关键点
- 响应式核心是依赖收集(track)和触发更新(trigger),使用 WeakMap、Map、Set 三层结构存储依赖关系
- Proxy 和 Reflect 用于拦截对象的 get 和 set 操作,自动执行 track 和 trigger
- activeEffect 变量保存当前执行的副作用函数,解决依赖收集时的函数引用问题
- ref 通过对象访问器(getter/setter)实现基本类型的响应式
- computed 本质是一个特殊的 ref,内部通过 effect 自动收集依赖并更新计算结果
目录