Vue3 源码解析 06上篇--响应式 baseHandler

525 阅读4分钟

Vue3 源码解析 06上篇--响应式 baseHandler

前言

之前学习了一下 reactive 和 ref 的源码实现。通过源码我们知道在 reactive 的实现的过程中是通过 Proxy 的数据劫持来实现的响应式。这里我们看一下 Proxy 的定义:

Proxy 对象用户创建一个对象的代理,从而是心啊基本操作的拦截和自定义(如属性的查找、赋值、枚举、函数调用等)。Proxy 接收两个参数targethandler。target 是我们要包装的目标对象。handler 通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理生成对象的行为。

下面我们就看一下 reactive 中的 处理器对象handler源码的实现。

这里先复习一下我们的目标数据类型

const enum TargetType {
  INVALID = 0, //其他类型
  COMMON = 1, //Array、Object类型的基本数据类型
  COLLECTION = 2 //Set 、 Map 、 WeakMap 、 WeakSet类型的集合数据类型
}

PS:因为篇幅比较长,所以这里分两部分来写。

baseHandlers 源码实现

baseHandlers 主要是针对基本数据类型。其主要包含四种 handler:mutableHandlers、readonlyHandlers、shallowReactiveHandlers、shallowReadonlyHandlers,通过方法名称我们应该也能看出,这四种是针对目标数据 target 的普通类型、readonly、shallow 等类型的。其实除 mutableHandlers 外的三种对象都可以看作是 mutableHandlers 的变种。 所以,这里我们先看一下 mutableHandlers 的源码实现:

//创建Object类型和Array类型的响应式Proxy使用
export const mutableHandlers: ProxyHandler<object> = {
  get, //creatorGetter()
  set, //createSetter()
  deleteProperty,
  has,
  ownKeys
}

我们知道 mutableHandlers 对象就是处理对象handler,所以 mutableHandlers 里面这几个方法的作用我们可以看一下官方描述:

  • handlers.get:属性读取操作的捕捉器
  • handler.set:属性设置操作的捕捉器
  • handler.deleteProperty:delete 操作符的捕捉器
  • handler.ownKeys:用于拦截 Object.getOwnPropertyNames 、Object.getOwnPropertySymbols 、Object.keys()、Reflect.ownKeys()等操作

这里一共是五个方法。我们直接按照源码的顺序来看一下。

createGetter

先看一下 get 源码:

function createGetter(isReadonly = false, shallow = false) {
  //target:待处理数据,key:数据的key
  return function get(target: Target, key: string | symbol, receiver: object) {
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (
      key === ReactiveFlags.RAW &&
      receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
    ) {
      return target
    }

    const targetIsArray = isArray(target)
    if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
  //通过Reflect拿到原始的get行为
    const res = Reflect.get(target, key, receiver)

    //如果是内置方法,不需要另外进行处理
    if (
      isSymbol(key)
        ? builtInSymbols.has(key as symbol)
        : key === `__proto__` || key === `__v_isRef`
    ) {
      return res
    }

    //如果不是readonly,track用于依赖收集
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }
    if (shallow) {
      return res
    }
    //如果是ref对象,
    if (isRef(res)) {
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }
    //如果是嵌套对象,另外处理
    if (isObject(res)) {
      //创建响应式的对象,传入的isReadonly是false
      //如果是嵌套对象的情况,通过递归调用reactive拿到结果
      return isReadonly ? readonly(res) : reactive(res)
    }
    return res
  }
}

get 函数很简单:就是判断根据传入的 key 返回对应的数据。

下面,我们简单分析一下 createGetter 这段代码:

  • 首先 通过 Reflect.get ,拿到原始的 get 方法
  • 判断如果是内置方法,不需要另外进行处理
  • 接下来,如果不是 readonly,则进行依赖收集
  • 判断是否是 ref 对象。这里有个额外的判断:对于非 shallow 模式中,对于 target 不是数组,会直接拿 ref.value 的值,而不是 ref
  • 最后,如果是嵌套对象,根据是 isReadonly 参数分别进行额外处理。

createSetter

紧接着我们看一下 createSetter 的源码。 在看 createSetter 之前,我们先复习一下 trigger 的类型

export const enum TriggerOpTypes {
  SET = 'set',
  ADD = 'add',
  DELETE = 'delete',
  CLEAR = 'clear'
}

这里分别对应了四种类型:修改、新增、删除、清空(在集合类型数据中发生)

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    //拿到原始值oldValue
    const oldValue = (target as any)[key]
    //如果对于target是对象,oldValue是ref,value不是ref,直接把value设置给oldValue.value
    if (!shallow) {
      value = toRaw(value)
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value
        return true
      }
    } else {
      //在shallow模式下,不论对象是否是响应式的,对象都按照原样设置
    }
    //原始对象里是否有新赋值的这个key
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    //通过reflect拿到原始的set方法
    const result = Reflect.set(target, key, value, receiver)
    //在不做任何触发监听函数的行为的情况下操作原型链的数据
    if (target === toRaw(receiver)) {
      //没有这个key,则添加属性
      //否则给原始属性赋值
      //trigger用于通知deps,通知依赖这一状态的对象更新
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

这里简单分析一下 createSetter 的源码:

  • 首先,在非 shallow 情况下,如果原来的对象不是数组,旧值是 ref 且新值不是 ref。则直接修改 ref.value
  • 在不做任何触发监听函数行为的情况下,操作原型链的数据,这些操作主要包括:
    • 在原始数据不包含当前需要修改的 key 的情况下,触发依赖更新,触发类型设置为 add
    • 在原始数据包含当前需要修改 key 的情况下,触发依赖更新,触发类型设置为 set
  • 最后,返回 Reflect 拿到的原始 set 行为

PS:trigger 的作用之前的文章介绍过,这里就不多赘述了

deleteProperty

这里的 deleteProperty 就是对 delete 操作符的劫持。我们简单看一下他的实现

function deleteProperty(target: object, key: string | symbol): boolean {
  const hadKey = hasOwn(target, key)
  const oldValue = (target as any)[key]
  const result = Reflect.deleteProperty(target, key)
  if (result && hadKey) {
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
  }
  return result
}

这段代码非常简明:显示判断当前数据有无需要删除的 key,然后获取对应的 value。如果两者都存在则调用 trigger 方法,触发更新依赖操作。最后返回 Reflect 获取的 deleteProperty 原始行为。

PS:因为 has 和 ownKeys 的源码比较简单,这里就不过多赘述了。

readonlyHandlers

我们都知道 mutableHandlers 对应的是普通响应式数据,那么 readonlyHandlers 对应的就是只读 readonly 数据。下面我们来看一下他的简单实现:

//packages/reactivity/src/baseHandlers.ts
//创建readonly类型数据的响应式
export const readonlyHandlers: ProxyHandler<object> = {
  get: readonlyGet,
  set(target, key) {
    if (__DEV__) {
      console.warn(
        `Set operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  },
  deleteProperty(target, key) {
    if (__DEV__) {
      console.warn(
        `Delete operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  }
}

这里的源码很简单,set 和 deleteProperty 方法在开发模式下都会给出相应的提示,告诉用户当前数据不可操作。

对于 get 方法,从我们上面的代码可知,不会进行 track 操作:

function createGetter(isReadonly = false, shallow = false) {
  //这里,如果是readonly,则不会进行track操作
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }
}

至于 shallowReactiveHandlers 和 shallowReadonlyHandlers 源码也是大同小异的,这里就不过多赘述了。

小结

简单总结一下 baseHandlers 就是:

  • 通过 Proxy 进行数据劫持返回新的响应式数据
  • 通过 Reflect 将我们响应式数据的操作反射到原始数据上
  • 读写过程中触发依赖收集和监听方法