Vue3响应式API-reactive的原理

这段代码实现了一个简化版的响应式系统 (类似 Vue 3 的 reactive),包含依赖收集和触发更新的核心机制。让我详细讲解每个部分:

一、核心结构

1. reactive 函数

typescript 复制代码
function reactive<T extends object>(target: T) {
  return createReactiveObject(target);
}
  • 入口函数,接收一个普通对象
  • 返回该对象的响应式代理
  • 使用泛型 T extends object 确保只能处理对象类型

2. createReactiveObject 函数

创建 Proxy 代理对象的核心函数:

typescript 复制代码
function createReactiveObject<T extends object>(target: T) {
  const handler = {
    // get 拦截器
    get(target: object, key: keyof object, receiver: () => void) {
      const result = Reflect.get(target, key, receiver);
      track(target, key);  // 依赖收集
      if (typeof result === "object" && result !== null) {
        return createReactiveObject(result);  // 深度响应式
      }
      return result;
    },
    
    // set 拦截器
    set(target: object, key: keyof object, value: unknown, receiver: () => void) {
      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);
}

关键点:

  • 深度响应式:当访问的属性值是对象时,递归创建响应式代理
  • 惰性转换:只有在访问嵌套对象时才进行响应式转换
  • Reflect API :使用 Reflect.get/set 确保正确的 this 绑定

二、依赖管理系统

1. 存储结构

typescript 复制代码
const targetMap = new WeakMap<object, Map<string, Set<Function>>>();
let activeEffect: null | Function = null;

结构说明:

text 复制代码
WeakMap
  key: 原始对象 (target)
  value: Map
    key: 属性名 (string)
    value: Set<Function>  // 依赖该属性的 effect 集合

为什么用 WeakMap?

  • 键是对象,不影响垃圾回收
  • 当原始对象不再使用时,对应的依赖关系会自动清除

2. track - 依赖收集

typescript 复制代码
function track(target: object, key: string) {
  if (!activeEffect) return;  // 没有 activeEffect 时不收集
  
  // 获取或创建 target 对应的 depsMap
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }
  
  // 获取或创建 key 对应的 dep(effect 集合)
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }
  
  dep.add(activeEffect);  // 将当前 effect 加入依赖集合
}

3. trigger - 触发更新

typescript 复制代码
function trigger(target: object, key: keyof object) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  
  const dep = depsMap.get(key);
  if (!dep) return;
  
  // 创建副本避免无限循环
  const effects = new Set(dep);
  effects.forEach((effect) => {
    if (effect !== activeEffect) {  // 避免当前 effect 触发自身
      effect();
    }
  });
}

三、Effect 系统

typescr 复制代码
export function effect(fn: Function) {
  const effectFn = () => {
    const prevEffect = activeEffect;
    activeEffect = effectFn;  // 设置当前活跃的 effect

    try {
      return fn();
    } finally {
      activeEffect = prevEffect;  // 恢复之前的 effect
    }
  };
  
  effectFn();  // 立即执行一次,进行初始依赖收集
  return effectFn;
}

执行流程:

  1. 创建 effectFn 包装函数
  2. 执行时设置 activeEffect = effectFn
  3. 执行用户传入的 fn()
  4. 在 fn() 执行期间,所有对响应式属性的访问都会调用 track
  5. track 将当前 activeEffect 收集为依赖
  6. 执行完成后恢复之前的 activeEffect

四、使用示例

typescript 复制代码
const state = reactive<{ todos: string[] }>({ todos: [] });

// 创建一个 effect
effect(() => {
  console.log('todos 改变了:', state.todos);
  // 首次执行时,访问 state.todos,触发 get
  // track 收集当前 effect 作为 todos 的依赖
});

// 修改状态
state.todos.push('学习响应式原理');  // 触发 set -> trigger -> 执行 effect

五、完整工作流程

  1. 初始化响应式对象

    typescript 复制代码
    const state = reactive({ todos: [] });
    // 创建 Proxy 代理
  2. 创建 effect

    typescript 复制代码
    effect(() => console.log(state.todos));
    // 1. 设置 activeEffect = 当前 effect
    // 2. 执行回调,访问 state.todos
    // 3. Proxy.get 触发,track 收集依赖
  3. 数据变更

    types 复制代码
    state.todos = ['新任务'];
    // 1. Proxy.set 触发
    // 2. trigger 查找依赖的 effects
    // 3. 执行所有依赖的 effect 函数
相关推荐
掘金酱5 分钟前
【10.24 特别活动】AI 时代,程序员的新饭碗在哪里?
前端·人工智能·后端
excel14 分钟前
多进程环境下使用 PM2 部署 Socket.IO:需要注意什么
前端
IT_陈寒28 分钟前
Redis的原子性竟然失效了?记一次分布式锁翻车实录
前端·人工智能·后端
时间的情敌2 小时前
《回顾 2018 前端:Vue2 成熟期,Webpack4 发布,PWA 大火》
前端
Java后端的Ai之路2 小时前
前端技术Vue-09-webpack-vuejs-实战
前端·vue.js·webpack
LMT流氓兔2 小时前
第五单元——第4课:给工具参数加校验
java·前端·算法·ai·langchain
涛涛ing3 小时前
React Compiler正式“Rust化”:你项目里的Babel插件,正在被编译进打包器
前端
迷迭绿3 小时前
Cesium 轨迹回放播放器
前端·javascript·cesium
HolySaga3 小时前
我用 UTS 写了一个三端同构的蓝牙插件(Android / iOS / 鸿蒙 NEXT),把踩过的坑都讲给你听
前端
程序员老赵3 小时前
Docker 部署 XWiki:轻松搭建企业知识库与协作 Wiki
前端·docker·开源