reactive—将数据变成相应式数据

337 阅读4分钟

与ref用法类似,

不同点:

  • ref用于基本数据类型
  • reactive 用于复杂数据类型
export function reactive(target: object) {
  // 如果是readonly对象的代理,那么这个对象是不可观察的,直接返回readonly对象的代理
  if (readonlyToRaw.has(target)) {
    return target
  }
  // 如果是readonly原始对象,那么这个对象也是不可观察的,直接返回readonly对象的代理,这里使用readonly调用,可以拿到readonly对象的代理
  if (readonlyValues.has(target)) {
    return readonly(target)
  }

  // 调用createReactiveObject创建reactive对象
  return createReactiveObject(
    target, // 目标对象
    rawToReactive, // 原始对象映射响应式对象的WeakMap
    reactiveToRaw, // 响应式对象映射原始对象的WeakMap
    mutableHandlers, // 响应式数据的代理handler,一般是Object和Array
    mutableCollectionHandlers // 响应式集合的代理handler,一般是Set、Map、WeakMap、WeakSet
  )
}
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  if (!isObject(target)) {
    return target
  }
  // 如果已经是proxy对象则直接返回,有个例外,如果是readOnly作用于响应式
  if (
    target[ReactiveFlags.RAW] && 
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE]) 
  ) {
    return target
  }
  const proxyMap = isReadonly ? readonlyMap : reactiveMap
  // 已经有了对应的proxy映射 直接
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  const targetType = getTargetType(target)
  // 只有在白名单中的数据类型才可以被响应式
  if (targetType === TargetType.INVALID) {
    return target
  }
  // 通过Proxy API劫持target对象,把它变成响应式
  const proxy = new Proxy(
    target,
    // Map Set WeakMap WeakSet用collectionhandlers代理 Object Array用baseHandlers代理
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  // 存储一个原始类型和proxy数据类型的映射 
  proxyMap.set(target, proxy)
  return proxy
}

可相应式数据类型白名单

function targetTypeMap(rawType: string) {
  switch (rawType) {
    case 'Object':
    case 'Array':
      return TargetType.COMMON // 1
    case 'Map':
    case 'Set':
    case 'WeakMap':
    case 'WeakSet':
      return TargetType.COLLECTION // 2
    default:
      return TargetType.INVALID // 0
  }
}

总结:

reactive创建对象的过程

\

  1. 如果传入的是只读类型的响应式对象 直接返回该响应式对象

  2. 否则执行createReactiveObject 方法
    a. 如果不是对象则返回(并报警告)
    b. 如果已经是proxy对象则直接返回,有个例外,如果是readOnly作用于响应式
    c. 判断是否存在代理映射,从相应的map集合(readonlyMap、reactiveMap)中取值,并返回,若无,继续向下执行
    d. 判断数据类型是否是白名单类型,若不是,直接返回(0->代表为非白名单类型,1->基本数据类型(包括数组和对象),2->集合类型)
    e. 若是,可通过Proxy API劫持target对象,把它变成响应式,并将其存储至proxyMap集合中

    image

集合类型处理器mutableHandlers(collectionHandlers)

这个方法实际上就是对目标对象的一些访问、删除、查询、设置的操作的劫持

const get = /*#__PURE__*/ createGetter()
const set = /*#__PURE__*/ createSetter()
export const mutableHandlers: ProxyHandler<object> = {
  get, // 对数据的读取属性进行拦截 包含target.语法和target[]
  set, // 对数据的存入属性进行拦截
  deleteProperty, // delete操作符进行拦截 可以监听到属性的删除操作
  has, // 对对象的in操作符进行属性拦截
  ownKeys // 访问对象属性名的时候会触发ownKeys函数
}

在这主要看,get和set

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // 求值 
    const res = Reflect.get(target, key, receiver)

    if (!isReadonly) {
      // 依赖收集
      track(target, TrackOpTypes.GET, key)
    }
    // 递归调用响应式
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }
    // 返回结果
    return res
  }
}

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    // 1.先获取oldValue
    const oldValue = (target as any)[key]
    // 2.设置新值
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    // 派发更新
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

首先会使用Reflect.get进行求值, 然后判断是否是只读的,

如果不是就调用track进行依赖收集,然后对求值的结果进行判断,

如果是对象则递归调用reactive或者readonly对结果继续进行响应式处理,最后将获取的结果返回。

注意:

  • vue2.0 初始化就对整个对象进行递归相应
  • vue3.0 初始化时只对第一层属性进行相应式,当返回proxy的属性被访且是对象的话再进行递归响应式,Proxy劫持的是对象本身,并不能劫持子对象的变化,正是利用这种特性可以延时定义子对象响应式的实现,在初始化的时候性能也会得到提升。

参考文档:zhuanlan.zhihu.com/p/302380397