vue中Reactive定义深层对象数据,为什么解构后还具有响应式?

732 阅读4分钟

大家好,我是前端小张同学,最近在学习vue时,偶然发现一个问题,reactive定义的对象数据,解构完成之后,那个对象居然还是 Proxy代理对象,并不是一个普通对象,当时就匪夷所思,why? 于是我又去看了一下源码,终于得出了答案。

话不多说,直接上源码。

1:reactive是怎么做拦截处理的?

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  if (!isObject(target)) {  // 如果不是一个对象 则 返回当前 traget 
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  if (
    target[ReactiveFlags.RAW] && // 如果target 已经是一个 代理对象 则 返回当前对象
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only specific value types can be observed.
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  const proxy = new Proxy(
    target,
    // 重点 在这个 targetType 会进行 选择不用的 handles
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}

重点:

targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers

这段代码决定了,传入的object会被以什么样的形式去做拦截,vue提供了两种形式

  1. mutableHandlers 基本类型的拦截器
  2. mutableCollectionHandlers 针对子集合 Map 、WeakMap、set、weakSet 类型 提供的拦截器

1.1:mutableHandlers 实现

源码路径: core\packages\reactivity\src\baseHandlers.ts

image.png

1.2:mutableCollectionHandlers实现

源码路径:core\packages\reactivity\src\collectionHandlers.ts

image.png

好,接下来,我将一步一步带着你看 get 的实现,有啥差异,请随我一起!!!

2: mutableHandlers get 实现

源码路径:core\packages\reactivity\src\baseHandlers.ts

const get = /*#__PURE__*/ createGetter()

function createGetter(isReadonly = false, shallow = false) {
  // 重写 get访问器 返回一个 新的 get函数
  return function get(target: Target, key: string | symbol, receiver: object) { //传入 目标对象 , 访问的 key  和 receiver 代理对象
    if (key === ReactiveFlags.IS_REACTIVE) { // 判断 当前访问的对象的key 是不是一个 响应式的值 如果是 则返回 true 不用操作,说明已经代理过
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) { // 如果是一个只读的属性 则 直接返回 true 无需操作
      return isReadonly
    } else if (key === ReactiveFlags.IS_SHALLOW) {
      return shallow
    } else if (
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target
    }

    const targetIsArray = isArray(target) // target 是否是一个数组类型数据

    if (!isReadonly) { // 判断是否是 只读 属性 如果非只读的属性 说明可以进行操作 
      if (targetIsArray && hasOwn(arrayInstrumentations, key)) { // 是数组 并且 当前属性 是本身的属性 则 通过key去获取
        return Reflect.get(arrayInstrumentations, key, receiver)
      }
      if (key === 'hasOwnProperty') {
        return hasOwnProperty
      }
    }

    const res = Reflect.get(target, key, receiver) // 通过 Reflect 获取 target 目标对象的值

    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { // 判断 访问的 对象是否是 Symbol , 是的话 并且set集合中存在 当前 symbol 的值 则返回
      return res
    }

    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    if (shallow) { // 
      return res
    }

    if (isRef(res)) { // 如果是 ref 则自动解包获取 Value  
      // ref unwrapping - skip unwrap for Array + integer key.
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }

    if (isObject(res)) { // 如果是 object 则 通过 reactive 递归实现 代理
      // Convert returned value into a proxy as well. we do the isObject check
      // here to avoid invalid value warning. Also need to lazy access readonly
      // and reactive here to avoid circular dependency.
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res //经过上面的步骤 所有情况都已经处理完成 最终将 处理完的 目标对象 返回
  }
}

总结一下 createGetter 干了什么?

  1. 返回一个 get 函数
  2. 边界处理,可以直接返回 target的情况,无需操作
  3. 判断target 是否是数组类型,数组类型,则重写实现数组方法,进行原始参数调用
  4. 判断 访问的 对象是否是 Symbol , 是的话 并且set集合中存在 当前 symbol 的值 则返回
  5. 判断 获取的目标对象 是不是 一个 ref,如果是 则 自动解包
  6. 如果是 对象 则 通过 reactive 进行递归代理。
  7. 经过上面的步骤 所有情况都已经处理完成 最终将 处理完的 目标对象 返回

3:mutableCollectionHandlers get实现

处理 Map 、WeakMap、set、weakSet 这种特殊情况实现就比较容易了,边界处理 + 获取对象值


function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
  const instrumentations = shallow
    ? isReadonly
      ? shallowReadonlyInstrumentations
      : shallowInstrumentations
    : isReadonly
    ? readonlyInstrumentations
    : mutableInstrumentations

  return (
    target: CollectionTypes,
    key: string | symbol,
    receiver: CollectionTypes
  ) => {
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (key === ReactiveFlags.RAW) {
      return target
    }

    return Reflect.get(
      hasOwn(instrumentations, key) && key in target
        ? instrumentations
        : target,
      key,
      receiver
    )
  }
}

image.png

3:总结

所以现在知道了,为什么 reactive 解构完成的对象,还具备响应式,并且 pinia 中的 state 定义的 对象类型,通过解构 state 也具响应式, 是因为被深度递归代理,所以这也是reactive的一部分实现,在面试中也比较重要,希望能给大家带来收获。

加入我们的开发群,添加我微信,备注加群,进入我们内部交流群,了解更多前端知识。