vue3源码分析之reactive

60 阅读5分钟

reactive:

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

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

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

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

// 在这里是命名了一个 WeakMap 对象,WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。具体与 Map 对象的去区别可参考 MDNexport 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 的入口函数,最终是调用了自定义的 getset 方法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();    }  }}