实现Vue reactive响应式

手写实现Vue 3的reactive响应式系统,理解Proxy和依赖收集的原理

问题

Vue 3使用Proxy实现响应式系统,当数据变化时自动触发依赖更新。本题要求实现一个简化版的reactive函数,支持:

  1. 对象的响应式转换
  2. 依赖收集(track)
  3. 依赖触发(trigger)
  4. 嵌套对象的响应式处理

解答

// 存储当前正在执行的effect函数
let activeEffect = null;

// 存储依赖关系的WeakMap: target -> Map(key -> Set(effect))
const targetMap = new WeakMap();

/**
 * 依赖收集函数
 * @param {Object} target - 目标对象
 * @param {String} key - 属性名
 */
function track(target, key) {
  if (!activeEffect) return;
  
  // 获取target对应的依赖Map
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
  
  // 获取key对应的依赖Set
  let dep = depsMap.get(key);
  if (!dep) {
    dep = new Set();
    depsMap.set(key, dep);
  }
  
  // 添加当前effect到依赖集合
  dep.add(activeEffect);
}

/**
 * 触发依赖更新
 * @param {Object} target - 目标对象
 * @param {String} key - 属性名
 */
function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  
  const dep = depsMap.get(key);
  if (!dep) return;
  
  // 执行所有依赖的effect函数
  dep.forEach(effect => effect());
}

/**
 * 创建响应式对象
 * @param {Object} target - 原始对象
 * @returns {Proxy} 响应式代理对象
 */
function reactive(target) {
  // 如果不是对象,直接返回
  if (typeof target !== 'object' || target === null) {
    return target;
  }
  
  const handler = {
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver);
      
      // 收集依赖
      track(target, key);
      
      // 如果属性值是对象,递归转换为响应式
      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;
    }
  };
  
  return new Proxy(target, handler);
}

/**
 * 副作用函数
 * @param {Function} fn - 需要执行的函数
 */
function effect(fn) {
  activeEffect = fn;
  fn(); // 立即执行一次,收集依赖
  activeEffect = null;
}

// 导出API
export { reactive, effect };

使用示例

// 示例1: 基本使用
const state = reactive({
  count: 0,
  message: 'Hello'
});

effect(() => {
  console.log('count changed:', state.count);
});

state.count++; // 输出: count changed: 1
state.count++; // 输出: count changed: 2

// 示例2: 嵌套对象
const user = reactive({
  name: 'Tom',
  profile: {
    age: 18,
    address: {
      city: 'Beijing'
    }
  }
});

effect(() => {
  console.log('User info:', user.name, user.profile.age);
});

user.profile.age = 20; // 输出: User info: Tom 20

// 示例3: 多个effect
const data = reactive({ price: 10, quantity: 2 });

effect(() => {
  console.log('Total:', data.price * data.quantity);
});

effect(() => {
  console.log('Price:', data.price);
});

data.price = 20; 
// 输出: Total: 40
// 输出: Price: 20

// 示例4: 模拟Vue组件更新
const state = reactive({
  todos: [
    { id: 1, text: 'Learn Vue', done: false },
    { id: 2, text: 'Build App', done: false }
  ]
});

// 模拟渲染函数
effect(() => {
  console.log('Rendering todos:', state.todos.length);
  state.todos.forEach(todo => {
    console.log(`- ${todo.text}: ${todo.done ? '✓' : '○'}`);
  });
});

// 修改数据触发重新渲染
state.todos[0].done = true;
state.todos.push({ id: 3, text: 'Deploy', done: false });

关键点

  • Proxy代理: 使用Proxy拦截对象的get和set操作,实现响应式的机制

  • 依赖收集(track): 在get拦截器中收集依赖,使用WeakMap -> Map -> Set的三层结构存储依赖关系

  • 依赖触发(trigger): 在set拦截器中触发依赖更新,执行所有相关的effect函数

  • activeEffect: 全局变量记录当前执行的effect函数,用于建立数据与effect的关联

  • 递归响应式: 对嵌套对象进行递归处理,确保深层属性也具有响应式能力

  • Reflect使用: 使用Reflect API确保正确的this指向和操作行为

  • 数据结构设计: WeakMap避免内存泄漏,Map存储属性依赖,Set存储effect函数避免重复

  • 立即执行: effect函数创建时立即执行一次,完成初始依赖收集