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 自动收集依赖并更新计算结果