Vue3 源码解析系列 - reactive

280 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第12天,点击查看活动详情

前言

我们知道 Vue3 的响应式原理采用的是 Proxy 来代理对象,通过劫持方法来实现响应式,而 reactive 的作用就是返回对象的响应式副本。

使用

对传入的对象进行响应式转换,这个转换是深层的,它影响这个对象所有嵌套的属性。

const obj = reactive({ count: 1 })

源码

源码的位置在,/packages/reactivity/src/reactive.ts

// packages/reactivity/src/reactive.ts
export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (isReadonly(target)) {
    return target
  }
  return createReac tiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}

首先会判断这个对象是否是只读的,如果是会直接方法这个对象。只读是通过对象中是否有 ReactiveFlags.IS_READONLY 来判断的,ReactiveFlags 枚举非常常用,我们可以看下它的值。

export const enum ReactiveFlags {
  SKIP = '__v_skip', // 是否跳过响应式 返回原始对象
  IS_REACTIVE = '__v_isReactive', // 标记一个响应式对象
  IS_READONLY = '__v_isReadonly',// 标记一个只读对象
  IS_SHALLOW = '__v_isShallow',
  RAW = '__v_raw'// 标记获取原始值
}

createReactiveObject

createReactiveObject 是 reactive 的核心方法,最终返回这个代理对象。

/**
 * 
 * @param target 目标数据
 * @param isReadonly 是否只读
 * @param baseHandlers 生成代理对象的 handler 参数。当 target 类型是 Array 或 Object 时使用该 handler。
 * @param collectionHandlers 当 target 类型是 Map、Set、WeakMap、WeakSet 时使用该 handler。
 * @param proxyMap 存储生成代理对象后的 Map 对象。
 */
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  // 如果目标不是对象,直接返回原始值
  if (!isObject(target)) {
    return target
  }
  // 如果目标已经是一个代理,直接返回
  // 除非对一个响应式对象执行 readonly
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // 目标已经存在对应的代理对象
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // 只有白名单里的类型才能被创建响应式对象
  // Vue3 仅会对 Array、Object、Map、Set、WeakMap、WeakSet 生成代理,其他对象会被标记为 INVALID,
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}

先来看下它的5个参数。

  • target 目标数据
  • isReadonly 是否只读
  • baseHandlers 生成代理对象的 handler 参数。当 target 类型是 Array 或 Object 时使用该 handler。
  • collectionHandlers 当 target 类型是 Map、Set、WeakMap、WeakSet 时使用该 handler。
  • proxyMap 存储生成代理对象后的 Map 对象。
  1. 在方法中首先判断目标数据是否为对象,不是则直接返回原始值
  2. 判断目标是否已经是一个代理,如果是直接返回
  3. 目标已经存在对应的代理对象,直接返回
  4. 只有白名单里的类型才能被创建响应式对象
  5. 通过 Proxy 创建代理,并传入 Handlers。
  6. 最后返回这个 proxy

get handler

在 handler 中定义了劫持这个对象的操作方法,我们来看下具体实现。
baseHandlers 中处理 Array、Object 的数据类型,也是我们绝大部分时间使用 Vue3 时使用的类型。

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

get 通过 createGetter 进行创建。

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // 如果 get 访问的 key 是 '__v_isReactive',返回 createGetter 的 isReadonly 参数取反结果
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
       // 如果 get 访问的 key 是 '__v_isReadonly',返回 createGetter 的 isReadonly 参数
      return isReadonly
    } else if (key === ReactiveFlags.IS_SHALLOW) {
      return shallow
    } else if (
      // 如果 get 访问的 key 是 '__v_raw',并且 receiver 与原始标识相等,则返回原始值
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target
    }

    const targetIsArray = isArray(target)
    // arrayInstrumentations是一个对象,对象内保存了若干个被特殊处理的数组方法,并以键值对的形式存储。
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

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

    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }
    // 如果是 shallow 浅层响应式,直接返回 get 结果
    if (shallow) {
      return res
    }

    if (isRef(res)) {
      // ref unwrapping - does not apply for Array + integer key.
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }

    if (isObject(res)) {
      // 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
  }
}
  • 首先会通过 ReactiveFlags 的各种类型进行判断返回相应的值。
  • 通过映射const res = Reflect.get(target, key, receiver) 获取到对应key的值。
  • 使用 track 方法就行依赖收集,这个与副作用函数 effect 有关,我们后面再详细展开讲

set

set 的劫持也差不多。通过 createSetter 进行创建。 在 set 方法只,也是通过映射来设置值 const result = Reflect.set(target, key, value, receiver)
然后调用 trigger 方法来派发更新