vue3源码分析之reactive

文章详细解析了Vue3中reactive函数的实现,通过Proxy对象创建目标对象的代理,实现数据的拦截和自定义行为。get方法中进行依赖收集,set方法触发依赖更新。createReactiveObject方法核心是使用Proxy,并利用WeakMap优化缓存。baseHandlers包含了get和set方法,get方法不仅返回值,还处理依赖收集,而set方法则在修改数据后触发依赖更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

reactive:

一、这段是reactive方法的入口函数

export function reactive(target) {
  return createReactiveObject(target, reactiveMap, mutableHandlers);
}

二、createReactiveObjectreactive真正调用的方法,在这个方法中核心调用的是proxy

第一步、如果缓存中(proxyMap)存在target,就会从缓存中(proxyMap)返回target对应的值。

// 在这里是命名了一个 WeakMap 对象,WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。具体与 Map 对象的去区别可参考 MDN
export const reactiveMap = new WeakMap();

Map 对象与 WeakMap 对象区别可参考 MDN

第二步、如果缓存中不存在 target,将命中 proxy 方法,将通过 baseHandlers 方法处理后的 target 存入 proxyMap 中。接下来分析一下 Proxy 方法是如何工作的。

定义Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

语法:const p = new Proxy(target, handler)

参数:

target: 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

handler: 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

简单理解为,proxy 对象就是通过 handler 方法去操作 target ,最后返回被重新定义的 target 值。

function createReactiveObject(target, proxyMap, baseHandlers) {
  // 核心就是 proxy
  // 目的是可以侦听到用户 get 或者 set 的动作

  // 如果命中的话就直接返回就好了
  // 使用缓存做的优化点 
  const existingProxy = proxyMap.get(target);
  if (existingProxy) {
    return existingProxy;
  }

  const proxy = new Proxy(target, baseHandlers);

  // 把创建好的 proxy 给存起来,
  proxyMap.set(target, proxy);
  return proxy;
}

三、接下来分析,源码中 Proxy 对象调用的 baseHandlers 方法。

// mutableHandlers 的入口函数,最终是调用了自定义的 get 和 set 方法
export const mutableHandlers = {
  get,
  set,
};
1、get 方法分析:

第一步、判断当前 get 方法是被 reactive 触发还是 Readonly

第二步、命中 Reflect.get() 方法,从 target 对象中 获取 key 对应的值,返回该值为 res

定义:**Reflect.get()**方法与从 对象 (target[propertyKey]) 中读取属性类似,但它是通过一个函数执行来操作的。

语法Reflect.get(target, propertyKey[, receiver])

参数: target: 需要取值的目标对象

​ propertyKey: 需要获取的值的键值

​ receiver: 如果target对象中指定了getterreceiver则为getter调用时的this值。

简单理解该方法,从 target 对象中获取 propertyKey 对应的值,最后返回该值

第三步、如果是 reactive 触发的 get 方法,就触发 track 方法进行依赖收集(源码分析在下面)

第四步、判断 isReadonly,将 res 分别以 readonly(res) 和 reactive(res) 的形式包装返回

const get = createGetter();

export const enum ReactiveFlags {
  IS_REACTIVE = "__v_isReactive",
  IS_READONLY = "__v_isReadonly",
  RAW = "__v_raw",
}

function createGetter(isReadonly = false, shallow = false) {
  return function get(target, key, receiver) {
    // 此处的三个方法,是在判断 key 是属于哪个方法,且 receiver 是否存在于缓存中 
    const isExistInReactiveMap = () =>
      key === ReactiveFlags.RAW && receiver === reactiveMap.get(target);

    const isExistInReadonlyMap = () =>
      key === ReactiveFlags.RAW && receiver === readonlyMap.get(target);

    const isExistInShallowReadonlyMap = () =>
      key === ReactiveFlags.RAW && receiver === shallowReadonlyMap.get(target);

    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly;
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly;
    } else if (
      isExistInReactiveMap() ||
      isExistInReadonlyMap() ||
      isExistInShallowReadonlyMap()
    ) {
      return target;
    }

    const res = Reflect.get(target, key, receiver);

    // 问题:为什么是 readonly 的时候不做依赖收集呢
    // readonly 的话,是不可以被 set 的, 那不可以被 set 就意味着不会触发 trigger
    // 所有就没有收集依赖的必要了

    if (!isReadonly) {
      // 在触发 get 的时候进行依赖收集
      track(target, "get", key);
    }

    if (shallow) {
      return res;
    }

    if (isObject(res)) {
      // 把内部所有的是 object 的值都用 reactive 包裹,变成响应式对象
      // 如果说这个 res 值是一个对象的话,那么我们需要把获取到的 res 也转换成 reactive
      // res 等于 target[key]
      return isReadonly ? readonly(res) : reactive(res);
    }

    return res;
  };
}
此处为第三步的源码分析:
export function track(target, type, key) {
  if (!isTracking()) {
    return;
  }
  console.log(`触发 track -> target: ${target} type:${type} key:${key}`);
  // 1. 先基于 target 找到对应的 dep
  // 如果是第一次的话,那么就需要初始化
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    // 初始化 depsMap 的逻辑
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }

  let dep = depsMap.get(key);

  if (!dep) {
    dep = createDep();

    depsMap.set(key, dep);
  }

  trackEffects(dep);
}

export function trackEffects(dep) {
  // 用 dep 来存放所有的 effect

  // TODO
  // 这里是一个优化点
  // 先看看这个依赖是不是已经收集了,
  // 已经收集的话,那么就不需要在收集一次了
  // 可能会影响 code path change 的情况
  // 需要每次都 cleanupEffect
  // shouldTrack = !dep.has(activeEffect!);
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect);
    (activeEffect as any).deps.push(dep);
  }
}
2、set 方法分析

第一步、命中 Reflect.set 方法,向 target 对象中添加 value值,属性名称设置为 key

定义Reflect.set 方法允许你在对象上设置属性。它的作用是给属性赋值并且就像 property accessor 语法一样,但是它是以函数的方式。

语法Reflect.set(target, propertyKey, value[, receiver])

参数: target: 设置属性的目标对象。

​ propertyKey: 设置的属性的名称。

​ value: 设置的值。

​ receiver: 如果遇到 setterreceiver则为setter调用时的this值。

简单理解该方法,向 target 对象中添加 value值,属性名称设置为 propertyKey,最后返回一个 Boolean 值

第二步、命中 trigger 方法(在下方进行分析)

第三步、返回 result,这是一个 Boolean 值

const set = createSetter();

function createSetter() {
  return function set(target, key, value, receiver) {
    const result = Reflect.set(target, key, value, receiver);

    // 在触发 set 的时候进行触发依赖
    trigger(target, "set", key);

    return result;
  };
}
此处为第二步的源码补充分析:
export function trigger(target, type, key) {
  // 1. 先收集所有的 dep 放到 deps 里面,
  // 后面会统一处理
  let deps: Array<any> = [];
  // dep

  const depsMap = targetMap.get(target);

  if (!depsMap) return;

  // 暂时只实现了 GET 类型
  // get 类型只需要取出来就可以
  const dep = depsMap.get(key);

  // 最后收集到 deps 内
  deps.push(dep);

  const effects: Array<any> = [];
  deps.forEach((dep) => {
    // 这里解构 dep 得到的是 dep 内部存储的 effect
    effects.push(...dep);
  });
  // 这里的目的是只有一个 dep ,这个dep 里面包含所有的 effect
  // 这里的目前应该是为了 triggerEffects 这个函数的复用
  triggerEffects(createDep(effects));
}

export function triggerEffects(dep) {
  // 执行收集到的所有的 effect 的 run 方法
  for (const effect of dep) {
    if (effect.scheduler) {
      // scheduler 可以让用户自己选择调用的时机
      // 这样就可以灵活的控制调用了
      // 在 runtime-core 中,就是使用了 scheduler 实现了在 next ticker 中调用的逻辑
      effect.scheduler();
    } else {
      effect.run();
    }
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值