返回博客
技术 2025年2月3日 3 分钟阅读 · 653 字

Vue 3 Composition API 响应式原理:从 Proxy 到依赖收集

深入 Vue 3 响应式系统的底层实现:Proxy 代理、track 依赖收集、trigger 派发更新的完整流程。

#Vue.js #JavaScript #响应式 #源码分析
本文由 AI 辅助生成,经人工审核发布

从 Object.defineProperty 到 Proxy

Vue 2 使用 Object.defineProperty 实现响应式,存在两个致命缺陷:

  1. 无法检测属性新增/删除:需要 Vue.set() / Vue.delete()
  2. 无法监听数组索引和 length 变化:需要重写数组原型方法

Vue 3 用 Proxy 彻底解决了这些问题:

// Vue 3 响应式核心(简化版)
function reactive(target) {
  const handler = {
    get(target, key, receiver) {
      track(target, key);  // 依赖收集
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver);
      trigger(target, key);  // 派发更新
      return result;
    }
  };
  return new Proxy(target, handler);
}

Proxy 可以拦截 13 种操作(包括 hasdeletePropertyownKeys 等),比 Object.defineProperty 强大得多。

依赖收集:track 函数

track 的作用是建立「属性 → 依赖该属性的 effect」的映射关系:

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

function track(target, key) {
  if (!activeEffect) return;

  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }

  let dep = depsMap.get(key);
  if (!dep) {
    dep = new Set();
    depsMap.set(key, dep);
  }

  dep.add(activeEffect);
}

数据结构是三层 Map:

targetMap (WeakMap)
  └─ target → depsMap (Map)
                └─ key → dep (Set<effect>)

使用 WeakMap 是因为当 target 对象被销毁时,对应的依赖关系会自动被垃圾回收。

派发更新:trigger 函数

当响应式数据被修改时,trigger 找到所有依赖该属性的 effect 并执行:

function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;

  const dep = depsMap.get(key);
  if (dep) {
    dep.forEach(effect => effect());
  }
}

effect 与 computed

effect 是响应式系统的基础原语。computedwatch 都建立在 effect 之上:

// computed 的简化实现
function computed(getter) {
  let value;
  let dirty = true;

  const runner = effect(() => {
    value = getter();
    dirty = false;
  });

  return {
    get value() {
      if (dirty) {
        runner();  // 重新计算
      }
      return value;
    }
  };
}

computed 本质上是一个带缓存的 effect。当依赖的响应式数据变化时,trigger 会将 dirty 设为 true,下次访问 .value 时才重新计算。

ref 的实现

ref 是对原始值的包装。因为 Proxy 无法代理原始值(如 number、string),所以需要用对象包装:

function ref(value) {
  return {
    get value() {
      track(this, 'value');
      return value;
    },
    set value(newVal) {
      value = newVal;
      trigger(this, 'value');
    }
  };
}

这就是为什么访问 ref 需要 .value

性能优化:惰性响应式

Vue 3.2+ 引入了 shallowRefshallowReactive,只代理第一层:

const state = shallowReactive({ user: { name: 'Li' } });
// state.user 的修改不会触发响应式
// 需要手动触发:triggerRef(state, 'user')

在处理大型嵌套对象时,这可以显著减少 Proxy 代理的性能开销。

总结

Vue 3 的响应式系统是一个精妙的工程设计:

  • Proxy 提供了完整的拦截能力
  • WeakMap + Map + Set 三层结构实现了高效的依赖追踪
  • effect 作为基础原语,computed 和 watch 都建立其上
  • 惰性求值和 shallow 模式提供了细粒度的性能控制