Vue3.0源码解析「reactive」篇 — 2.reactive functions 响应式入口

833 阅读6分钟

reactive

reactive用于返回一个普通响应式代理(非只读或者禁止响应式代理)。

reactive

代码分析的入口就从 vue 提供的 reactive函数开始,先来看看 vue 官方的组合式 api 手册中对这个函数的说明:

接收一个普通对象然后返回该普通对象的响应式代理。

const obj = reactive({ count: 0 })

响应式转换是“深层的”:会影响对象内部所有嵌套的属性。基于 ES2015Proxy 实现,返回的代理对象不等于原始对象。

前文我们说过reactive 的实现是由 proxyeffect 组合,先来看一下 reactive 方法的定义:

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}

注意: 对一个 readonly 的代理对象使用 reactive,会返回这个只读代理对象本身。

const rOnly = readonly({ count: 1 });
const stillROnly = reactive(ronly);

stillROnly.count++;
// Cannot assign to 'count' because it is a read-only property.

createReactiveObject

其实本身 API 是很简单的,传入一个对象,返回一个其普通响应式代理对象,创建的方法是createReactiveObject

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  if (!isObject(target)) {
    // 过滤原始类型
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // 有 target[ReactiveFlags.RAW 证明已经是一个响应式代理,除非是对一个 reactive 对象调用 readonly 否则直接返回;
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only a whitelist of value types can be observed.
  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
}

createReactiveObject 最后返回的是一个 proxy 对象即 target 的响应式代理,具体看看这个函数的判断流程:

  • 如果传入一个原始类型(primitive),原始类型到响应式代理的转换要用 refreactive无法处理所以返回原对象;

  • 通过 target[ReactiveFlags.RAW] 判断传入的 target已经是是某种响应式代理类型,除非这个响应式对象原本是普通响应式代理而现在要设置为只读响应式代理,否则直接返回,也就是下面这几种情况会直接返回原代理:

    reactive(readonly) -> readonly
    readonly(readonly) -> readonly
    reactive(reactive) -> reactive
    
  • 尝试在 proxyMap 查看有target对应的响应式代理,有则返回;

  • 最后通过 getTargetType(全局变量中有提到) 判断 target是否属于能转换为响应式代理的类型,如果是则通过 proxy 进行转换,并且在 proxyMap 上做缓存;

    • 根据 target 类型的不同(object/Array or Map/Set/WeakMap/WeakSet)使用的 proxyHandler 也不同。

readonly

readonly 返回对象的只读代理,传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的。

触发只读代理的依赖通常是通过修改其引用的响应式代理:

const original = reactive({ count: 0 })

const copy = readonly(original)

// 依赖追踪
watchEffect(() => console.log(copy.count))

// original 上的修改会触发 copy 上的侦听
original.count++

// 无法修改 copy 并会被警告
copy.count++ // warning!

readonly 的函数定义没什么好说的,就是改了几个参数:

/**
 * Creates a readonly copy of the original object. Note the returned copy is not
 * made reactive, but `readonly` can be called on an already reactive object.
 */
export function readonly<T extends object>(
  target: T
): DeepReadonly<UnwrapNestedRefs<T>> {
  return createReactiveObject(
    target,
    true,
    readonlyHandlers,
    readonlyCollectionHandlers,
    readonlyMap
  )
}

tsvuereadonly 返回的类型做了约束,所以修改属性会在编译期间报错。

这个 DeepReadonly 类型的实现也很有意思。显示对 Builtin 内置类型做检查,这种类型不需要深度递归只解 readonly 包裹。然后对于 Map/Set/WeakSet/WeakMap/Promise 通过 infer 取出嵌套的类型然后给它加上 DeepReadonly,对于 Object 遍历所有属性然后递归 DeepReadonly

type Primitive = string | number | boolean | bigint | symbol | undefined | null
type Builtin = Primitive | Function | Date | Error | RegExp
export type DeepReadonly<T> = T extends Builtin
  ? T
  : T extends Map<infer K, infer V>
    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
    : T extends ReadonlyMap<infer K, infer V>
      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
      : T extends WeakMap<infer K, infer V>
        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>
        : T extends Set<infer U>
          ? ReadonlySet<DeepReadonly<U>>
          : T extends ReadonlySet<infer U>
            ? ReadonlySet<DeepReadonly<U>>
            : T extends WeakSet<infer U>
              ? WeakSet<DeepReadonly<U>>
              : T extends Promise<infer U>
                ? Promise<DeepReadonly<U>>
                : T extends {}
                  ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
                  : Readonly<T>

readonly(reactive(obj))

唯一会产生效果的嵌套响应式调用就是 readonly(reactive(obj)),这个东西返回的到底是什么呢?

type nested = {
  getter: readonlyGetter,
  setter: readonlySetter,
  [ReacticeFlags.RAW]: {
     getter: reactiveGetter,
     setter: reactiveSetter,
     [ReacticeFlags.RAW]: obj,
  } as ReactiveProxy
} as ReadonlyProxy

所以说访问 rorv 或者 rvgetter 都会收集依赖到同一个对象属性的 deps,而 setter 触发的也是同一个对象属性的 deps 中的 effects

shallowReactive

shallowReactive 只为某个对象的私有(第一层)属性创建浅层的响应式代理,不会对“属性的属性”做深层次、递归地响应式代理,而只是保留原样。

const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2,
  },
})

// 变更 state 的自有属性是响应式的
state.foo++
// ...但不会深层代理
isReactive(state.nested) // false
state.nested.bar++ // 非响应式

同样调用的 createReactiveObject,参数也没什么好说的:

export function shallowReactive<T extends object>(target: T): T {
  return createReactiveObject(
    target,
    false,
    shallowReactiveHandlers,
    shallowCollectionHandlers,
    shallowReactiveMap
  )
}

shallowReadonly

只为某个对象的自有(第一层)属性创建浅层的只读响应式代理,不会做深层次、递归地代理,深层次的属性并不是只读的。

const state = shallowReadonly({
  foo: 1,
  nested: {
    bar: 2,
  },
})

// 变更 state 的自有属性会失败
state.foo++
// ...但是嵌套的对象是可以变更的
isReadonly(state.nested) // false
isReactive(state.nested) // false
state.nested.bar++ // 嵌套属性依然可修改

同样调用的 createReactiveObject,参数也没什么好说的:

export function shallowReadonly<T extends object>(
  target: T
): Readonly<{ [K in keyof T]: UnwrapNestedRefs<T[K]> }> {
  return createReactiveObject(
    target,
    true,
    shallowReadonlyHandlers,
    shallowReadonlyCollectionHandlers,
    shallowReadonlyMap
  )
}

UtilFunc

isReadonly

检查一个对象是否是由 readonly 创建的只读代理,就是通过 ReactiveFlags.IS_READONLY 这个标志位,其实这个标志位是通过在 getter 里设置拦截实现的:

export function isReadonly(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}

// const get = /*#__PURE__*/ createGetter()
// function createGetter(isReadonly = false, shallow = false) {
//  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) return target

isReactive

检查一个对象是否是由 reactive 创建的响应式代理,分两种情况:

  • (value as Target)[ReactiveFlags.IS_REACTIVE]:说明 target 就是一个 reactive对象,返回 true
  • (value as Target)[ReactiveFlags.IS_READONLY] && isReactive((value as Target)[ReactiveFlags.RAW]) 说明是一个 readonly(reactive) 的嵌套对象,也返回 true
export function isReactive(value: unknown): boolean {
  if (isReadonly(value)) {
    return isReactive((value as Target)[ReactiveFlags.RAW])
  }
  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}

举个例子:

const rv = reactive({ count: 1 });
const rorv = readonly(rv);

console.log('readonly', isReadonly(rorv)); // readonly true
console.log('reactive', isReactive(rorv)); // reactive true

isProxy

查看是否设置任意一种代理:

export function isProxy(value: unknown): boolean {
  return isReactive(value) || isReadonly(value)
}

toRaw

toRaw 返回代理对象对应的实际对象,如果嵌套了多层代理,返回最终的实际对象,RAW 标志位也是通过在 getter 里设置拦截实现的。

export function toRaw<T>(observed: T): T {
  return (
    (observed && toRaw((observed as Target)[ReactiveFlags.RAW])) || observed
  )
}

markRaw

显式标记一个对象为“永远不会转为响应式代理”,函数返回这个对象本身。

const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false

// 如果被 markRaw 标记了,即使在响应式对象中作属性,也依然不是响应式的
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false

就是给这个对象添加一个 ReactiveFlags.SKIP 标志位,这个逻辑写在了 getTargetType 这个函数里:

export function markRaw<T extends object>(value: T): T {
  def(value, ReactiveFlags.SKIP, true)
  return value
}

export const def = (obj: object, key: string | symbol, value: any) => {
  Object.defineProperty(obj, key, {
    configurable: true,
    enumerable: false,
    value
  })
}