Vue3 源码阅读(7):响应式系统 —— 响应式 API 精讲

 我的开源库:

这篇文章详细讲讲 Vue3 中的响应式 API,如下图所示:

1,响应式:核心

1-1,ref

ref 的源码解析点击这里

1-2,computed

computed 的源码解析点击这里

1-3,reactive

源码如下所示,代码解释在注释中。

export function reactive(target: object) {
  // 如果当前的 target 参数是只读数据的话,直接 return target。
  // 只读数据无需转换成响应式的,因为只读数据不会变更,进而也就不会触发依赖执行,响应式的特征没有意义
  if (isReadonly(target)) {
    return target
  }
  // 调用 createReactiveObject 函数完成响应式数据的转换
  return createReactiveObject(
    // 转换的目标对象
    target,
    // 是否是只读的
    false,
    // 用于处理 Object 和 Array 数据类型的 Proxy handler
    mutableHandlers,
    // 用于处理 Map、Set、WeakMap、WeakSet 数据类型的 Proxy handler
    mutableCollectionHandlers,
    // 一个 Map 数据,键是转换成响应式的原生对象,值是其对应的响应式对象。
    // 这个数据起到一个响应式对象缓存的作用
    reactiveMap
  )
}
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  // 判断 target 是不是对象类型,如果不是的话,直接 return target
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // 如果 target 已经是一个代理数据的话,直接返回它。
  // 例外情况:readonly 类型的数据也会被当做响应式对象,直接返回。
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // 查看当前的 target 是不是已经被转化成响应式数据了,如果已经被转化成响应式数据的话,
  // 则直接返回缓存中的对应代理对象。
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // 获取当前 target 的数据类型,数据类型有三大类,第一类是常规代理数据,如:Object、Array
  // 第二大类是收集类,如:Map、Set、WeakMap、WeakSet
  // 第三大类是非法类,也就是说除了上面 6 种数据类型,其他数据都无法转换成响应式的。
  const targetType = getTargetType(target)
  // 如果当前的数据类型是 INVALID 的话,直接返回 target
  if (targetType === TargetType.INVALID) {
    return target
  }
  // 创建 Proxy 对象,并且根据 target 数据类型使用不同的 handler
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  // 将当前转换的响应式数据缓存到 proxyMap 中
  proxyMap.set(target, proxy)
  // 返回代理响应式数据
  return proxy
}

const enum TargetType {
  INVALID = 0,
  COMMON = 1,
  COLLECTION = 2
}

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

baseHandlers 和 collectionHandlers handler 如下所示:

// baseHandlers
export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}

// 代理收集类型的数据和 object、Array 是不一样的,收集类型的数据有专门的属性和方法进行数据的读写,
// 因此实现收集类数据响应式的关键是:重写收集类数据上的方法,重写的方法是使用 Proxy handler 中的 get,
// 当我们执行 map.set(xx, xx) 函数时,其实内部执行了两个操作,第一个操作是获取 map 中的 set 方法,
// 第二步才是执行这个方法,所以我们可以通过 get handler 自定义方法的获取逻辑,获取我们的重写方法。

// 当代理的对象是 Map、Set、WeakMap 和 WeakSet 时,使用如下的 handler
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
  get: /*#__PURE__*/ createInstrumentationGetter(false, false)
}

createInstrumentationGetter 函数的内容如下所示:

const [
  mutableInstrumentations,
  readonlyInstrumentations,
  shallowInstrumentations,
  shallowReadonlyInstrumentations
] = /* #__PURE__*/ createInstrumentations()

// 创建收集类数据的 Proxy get handler
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
  // instrumentations 是一个对象,这个对象的 key 是收集类数据的方法名,value 是对应的重写方法
  const instrumentations = shallow
    ? isReadonly
      ? shallowReadonlyInstrumentations
      : shallowInstrumentations
    : isReadonly
    ? readonlyInstrumentations
    : mutableInstrumentations
  // 收集类数据的代理 get handler,借助这个重写收集类数据的方法
  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
    }

    // 如果 key 是需要重写的方法名时,返回 instrumentations[key](返回重写的方法)
    return Reflect.get(
      hasOwn(instrumentations, key) && key in target
        ? instrumentations
        : target,
      key,
      receiver
    )
  }
}

1-4,readonly

readonly 的源码如下所示:

export function readonly<T extends object>(
  target: T
): DeepReadonly<UnwrapNestedRefs<T>> {
  return createReactiveObject(
    target,
    true,
    readonlyHandlers,
    readonlyCollectionHandlers,
    readonlyMap
  )
}

实现功能的重点在 readonlyHandlers 和 readonlyCollectionHandlers 这两个只读专属的 Proxy handlers。

readonlyHandlers 的内容如下所示:

export const readonlyHandlers: ProxyHandler<object> = {
  get: readonlyGet,
  set(target, key) {
    if (__DEV__) {
      warn(
        `Set operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  },
  deleteProperty(target, key) {
    if (__DEV__) {
      warn(
        `Delete operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  }
}

可以发现,实现很简单,当我们想变更数据的时候,打印出只读的警告,并且不做任何变更操作,直接 return true。

readonlyCollectionHandlers 的内容如下所示:

export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
  get: /*#__PURE__*/ createInstrumentationGetter(true, false)
}

可以发现,还是调用 createInstrumentationGetter 函数,只不过第一个参数 isReadonly 为 true。

// 创建收集类数据的 Proxy get handler
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
  // instrumentations 是一个对象,这个对象的 key 是收集类数据的方法名,value 是对应的重写方法
  const instrumentations = shallow
    ? isReadonly
      ? shallowReadonlyInstrumentations
      : shallowInstrumentations
    : isReadonly
    ? readonlyInstrumentations
    : mutableInstrumentations
  // 收集类数据的代理 get handler,借助这个重写收集类数据的方法
  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
    }

    // 如果 key 是需要重写的方法名时,返回 instrumentations[key](返回重写的方法)
    return Reflect.get(
      hasOwn(instrumentations, key) && key in target
        ? instrumentations
        : target,
      key,
      receiver
    )
  }
}

当第一个参数为 true 时,instrumentations 等于 shallowReadonlyInstrumentations 或者 readonlyInstrumentations,以 readonlyInstrumentations 为例。

const readonlyInstrumentations: Record<string, Function> = {
  get(this: MapTypes, key: unknown) {
    return get(this, key, true)
  },
  get size() {
    return size(this as unknown as IterableCollections, true)
  },
  has(this: MapTypes, key: unknown) {
    return has.call(this, key, true)
  },
  add: createReadonlyMethod(TriggerOpTypes.ADD),
  set: createReadonlyMethod(TriggerOpTypes.SET),
  delete: createReadonlyMethod(TriggerOpTypes.DELETE),
  clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
  forEach: createForEach(true, false)
}

function createReadonlyMethod(type: TriggerOpTypes): Function {
  return function (this: CollectionTypes, ...args: unknown[]) {
    if (__DEV__) {
      const key = args[0] ? `on key "${args[0]}" ` : ``
      console.warn(
        `${capitalize(type)} operation ${key}failed: target is readonly.`,
        toRaw(this)
      )
    }
    return type === TriggerOpTypes.DELETE ? false : this
  }
}

可以发现,当调用集合类只读数据上面能够变更数据的方法时,重写的方法中并不会进行真正的变更操作,只会打印出只读的警告。

1-5,watch

watch 的源码解析点击这里

1-6,watchEffect

watchEffect 的底层实现和 watch 是一样的,都是通过 doWatch 函数实现功能,这里就不过多赘述了,这里说说 flush: 'pre' | 'post' | 'sync' 是如何实现功能的。

当 flush 的值是 sync 时,回调函数会在响应式数据发生变化时同步执行

当 flush 的值是 pre 或者 post 时,回调函数会利用事件循环异步的执行,pre 和 post 的不同点是 pre 的回调函数会在组件渲染前被触发执行,而 post 类型的回调函数会在组件渲染完成后触发执行;

相关源码如下所示:

function doWatch(
  source: WatchSource | WatchSource[] | WatchEffect | object,
  cb: WatchCallback | null,
  { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): WatchStopHandle {
  let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
  const job: SchedulerJob = () => {
    ......
  }

  let scheduler: EffectScheduler
  if (flush === 'sync') {
    scheduler = job as any // the scheduler function gets called directly
  } else if (flush === 'post') {
    scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
  } else {
    // default: 'pre'
    scheduler = () => queuePreFlushCb(job)
  }

  const effect = new ReactiveEffect(getter, scheduler)

  ......
}

flush == 'sync'

当 flush 的值是 sync 时,scheduler 直接指向 job 函数,然后将 scheduler 函数作为第二个参数实例化 ReactiveEffect,当相关的响应式数据发生变更时,会直接执行 job 函数,所以,当 flush 的值是 sync 时,会同步的执行回调函数。

flush == 'pre',flush == 'post'

当 flush 的值是 pre 或者 post 时,回调函数的执行时异步的,并且和组件渲染的时机有关,当值是 pre 时,回调函数会在组件渲染前触发执行,当值是 post 时,回调函数会在组件渲染后触发执行。

内部的实现原理借助了 5 个用于存放回调函数的数组,分别是:

const queue: SchedulerJob[] = []

const pendingPreFlushCbs: SchedulerJob[] = []
let activePreFlushCbs: SchedulerJob[] | null = null

const pendingPostFlushCbs: SchedulerJob[] = []
let activePostFlushCbs: SchedulerJob[] | null = null

queue 数组的作用是:存放用于重新渲染组件的回调函数。

pendingPreFlushCbs 和 activePreFlushCbs 数组的作用是:存放需要组件重新渲染前执行的回调函数。

pendingPostFlushCbs 和 activePostFlushCbs 数组的作用是:存放需要组件重新渲染后执行的回调函数。

这些数组中存放的回调函数都是异步执行的,接下来看,执行的代码。

function queueFlush() {
  if (!isFlushing && !isFlushPending) {
    isFlushPending = true
    currentFlushPromise = resolvedPromise.then(flushJobs)
  }
}

利用 Promise 将 flushJobs 函数放入微任务队列中。

function flushJobs(seen?: CountMap) {
  // 执行 pre 回调函数
  flushPreFlushCbs(seen)

  try {
    // 执行 queue 中的回调函数,也就是能够重新渲染组件的回调函数
    for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
      const job = queue[flushIndex]
      if (job && job.active !== false) {
        callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
      }
    }
  } finally {
    flushIndex = 0
    queue.length = 0
    // 组件渲染完成后,执行 post 回调函数
    flushPostFlushCbs(seen)

    isFlushing = false
    currentFlushPromise = null
    // some postFlushCb queued jobs!
    // keep flushing until it drains.
    if (
      queue.length ||
      pendingPreFlushCbs.length ||
      pendingPostFlushCbs.length
    ) {
      flushJobs(seen)
    }
  }
}

在 flushJobs 函数中,写执行 pre 数组中的回调函数,然后执行 queue 数组中的回调函数,queue 中的函数执行完成后,再执行 post 数组中的回调函数,这样就保证了 pre 类型的回调函数在组件渲染前执行,post 类型的回调函数在组件渲染后执行。

1-7,watchPostEffect、watchSyncEffect

这两个 API 的实现原理就没有什么好说的了,看上面两小节的内容即可。

2,响应式: 工具

这一部分 API 的内部实现原理很简单,主要是利用标志位属性实现功能,所谓的标志位属性就是一个普通的属性,由 Vue3 对这个属性的意义进行定义,属性的值是 true 或者 false,例如当一个对象的 __v_isRef 属性为 true 时,则表明这个对象是一个 Ref 值。

2-1,isRef

isRef 接口的官方文档点击这里,该接口的源码如下所示:

class RefImpl<T> {
  public readonly __v_isRef = true

  ......
}
export function isRef(r: any): r is Ref {
  return !!(r && r.__v_isRef === true)
}

实现原理很简单,当 r.__v_isRef 属性的值为 true 时,则表明 r 是一个 Ref 值。

2-2,unref

unref 接口的官方文档点击这里

当参数是一个 ref 值时,读取并返回 ref.value,否则直接返回参数,源码如下所示:

export function unref<T>(ref: T | Ref<T>): T {
  return isRef(ref) ? (ref.value as any) : ref
}

2-3,toRef

toRef 接口的官方文档点击这里

这个接口的本质是利用访问器属性对源属性做一层代理,实现很简单,源码如下所示:

export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K,
  defaultValue?: T[K]
): ToRef<T[K]> {
  const val = object[key]
  return isRef(val)
    ? val
    : (new ObjectRefImpl(object, key, defaultValue) as any)
}

首先通过 object[key] 获取指定的属性值,然后判断 val 是不是一个 Ref,如果是的话,直接返回 val,如果不是的话,再通过 ObjectRefImpl 类做一层代理。

class ObjectRefImpl<T extends object, K extends keyof T> {
  public readonly __v_isRef = true

  constructor(
    private readonly _object: T,
    private readonly _key: K,
    private readonly _defaultValue?: T[K]
  ) {}

  get value() {
    const val = this._object[this._key]
    return val === undefined ? (this._defaultValue as T[K]) : val
  }

  set value(newVal) {
    this._object[this._key] = newVal
  }
}

ObjectRefImpl 类中实现功能的重点在 value 访问器属性上,首先看 get value(){},通过 this._object[this._key] 获取目标属性的值,然后这个值是 undefined 的话,返回默认值,如果不是 undefined 的话,则返回 val。

在 set value(){} 中,实现很简单, 直接将新值设置到 this._object[this._key] 上即可。

2-4,toRefs

toRefs 接口的官方文档点击这里

toRefs 接口的实现更加简单,只需要遍历参数对象/数组,对每个值都调用 toRef 函数进行处理即可,源码如下所示:

export function toRefs<T extends object>(object: T): ToRefs<T> {
  if (__DEV__ && !isProxy(object)) {
    console.warn(`toRefs() expects a reactive object but received a plain one.`)
  }
  const ret: any = isArray(object) ? new Array(object.length) : {}
  for (const key in object) {
    ret[key] = toRef(object, key)
  }
  return ret
}

首先判断参数是不是代理对象,如果不是的话,则打印出警告,这里即使参数不是响应式对象,toRefs 也会进行处理,并没有直接 return。

然后根据参数的数据类型,新建一个空数组或者空对象。

接下来对参数的属性进行遍历,每个属性都调用 toRef 进行代理,并将代理后的数据设置到 ret 中,最后 return ret 即可。

2-5,isProxy

isProxy 接口的官方文档点击这里

这个接口的实现原理是:根据标志位进行判断即可,源码如下所示:

// 判断 value 是不是一个代理对象
export function isProxy(value: unknown): boolean {
  // 使用的是 ||,所以下面的两个条件只要一个为 true,value 就会被判断为是一个代理对象。
  return isReactive(value) || isReadonly(value)
}

// 判断 value 是不是通过 reactive() 或者 shallowReactive() 创建出来的
export function isReactive(value: unknown): boolean {
  // 我们可能将一个响应式对象作为 readonly 函数的参数创建出一个只读的代理对象
  // 像这种只读对象 isReactive 函数也认为它是通过 reactive 或者 shallowReactive 创建出来的
  // 判断 value 是不是只读的
  if (isReadonly(value)) {
    // 如果是的话,将 value[ReactiveFlags.RAW] 作为 isReactive 函数的参数递归进行判断
    return isReactive((value as Target)[ReactiveFlags.RAW])
  }
  // 判断 value 中的 [ReactiveFlags.IS_REACTIVE] 属性标志位是否为 true 即可。
  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}

// 判断 value 是不是只读的
// 判断 value[ReactiveFlags.IS_READONLY] 属性标志位是否为 true 即可。
export function isReadonly(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}

源码解读看注释即可。

2-6,isReactive

// 判断 value 是不是通过 reactive() 或者 shallowReactive() 创建出来的
export function isReactive(value: unknown): boolean {
  // 我们可能将一个响应式对象作为 readonly 函数的参数创建出一个只读的代理对象
  // 像这种只读对象 isReactive 函数也认为它是通过 reactive 或者 shallowReactive 创建出来的
  // 判断 value 是不是只读的
  if (isReadonly(value)) {
    // 如果是的话,将 value[ReactiveFlags.RAW] 作为 isReactive 函数的参数递归进行判断
    return isReactive((value as Target)[ReactiveFlags.RAW])
  }
  // 判断 value 中的 [ReactiveFlags.IS_REACTIVE] 属性标志位是否为 true 即可。
  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}

2-7,isReadonly

// 判断 value 是不是只读的
// 判断 value[ReactiveFlags.IS_READONLY] 属性标志位是否为 true 即可。
export function isReadonly(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}

3,响应式: 进阶

3-1,shallowRef

shallowRef 接口的官方文档点击这里

建议先看下我的上一篇博客

源码如下所示:

export function shallowRef(value?: unknown) {
  // 这里需要关注的点是,createRef 函数的第二个参数为 true,这表明创建的是一个浅的 Ref
  return createRef(value, true)
}
function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}
class RefImpl<T> {
  private _value: T
  private _rawValue: T

  public dep?: Dep = undefined
  public readonly __v_isRef = true

  constructor(value: T, public readonly __v_isShallow: boolean) {
    this._rawValue = __v_isShallow ? value : toRaw(value)
    this._value = __v_isShallow ? value : toReactive(value)
  }

  get value() {
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    newVal = this.__v_isShallow ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = this.__v_isShallow ? newVal : toReactive(newVal)
      triggerRefValue(this, newVal)
    }
  }
}

实现功能的重点在 RefImpl 类中,在构造函数中,如果 __v_isShallow 属性为 true 的话,则不需要将 value 进行响应式的处理,这保证了对数据深层次的读写不会进行依赖收集以及依赖更新。

3-2,triggerRef

接口的官方文档点击这里

该接口的作用是:手动触发一个浅层 Ref 依赖的重新执行。

源码如下所示:

export function triggerRef(ref: Ref) {
  triggerRefValue(ref, __DEV__ ? ref.value : void 0)
}
export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
  ref = toRaw(ref)
  if (ref.dep) {
    triggerEffects(ref.dep)
  }
}
export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // spread into array for stabilization
  const effects = isArray(dep) ? dep : [...dep]
  for (const effect of effects) {
    if (effect.computed) {
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
  for (const effect of effects) {
    if (!effect.computed) {
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
}

function triggerEffect(
  effect: ReactiveEffect,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  if (effect !== activeEffect || effect.allowRecurse) {
    if (__DEV__ && effect.onTrigger) {
      effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
    }
    if (effect.scheduler) {
      effect.scheduler()
    } else {
      effect.run()
    }
  }
}

实现原理很简单,首先获取 Ref 值的 dep 属性,这个 dep 属性保存着使用了当前 ref 值的依赖(ReactiveEffect 实例),然后遍历 dep,触发 dep 重新执行即可。

3-3,customRef

customRef 的官方文档点击这里

customRef 接口的作用是让用户拥有能够重写 ref value 访问器属性的能力。

源码如下所示:

export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
  return new CustomRefImpl(factory) as any
}
class CustomRefImpl<T> {
  public dep?: Dep = undefined

  private readonly _get: ReturnType<CustomRefFactory<T>>['get']
  private readonly _set: ReturnType<CustomRefFactory<T>>['set']

  public readonly __v_isRef = true

  constructor(factory: CustomRefFactory<T>) {
    const { get, set } = factory(
      () => trackRefValue(this),
      () => triggerRefValue(this)
    )
    this._get = get
    this._set = set
  }

  get value() {
    return this._get()
  }

  set value(newVal) {
    this._set(newVal)
  }
}

export function trackRefValue(ref: RefBase<any>) {
  if (shouldTrack && activeEffect) {
    ref = toRaw(ref)
    trackEffects(ref.dep || (ref.dep = createDep()))
  }
}

export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
  ref = toRaw(ref)
  if (ref.dep) {
    triggerEffects(ref.dep)
  }
}

首先看最下面的两个工具函数,这两个函数的作用是对指定的 Ref 值进行依赖追踪和触发依赖,参数是一个 Ref 对象。

然后看 CustomRefImpl 的构造函数,我们需要执行 factory 函数,执行的时候,将能够进行依赖收集和触发依赖的两个函数作为参数传递进去,函数的返回值是一个对象,对象中有 get 和 set 两个函数,这两个函数就是用户重写的 value 计算属性函数,然后将 get 和 set 函数设置到对象实例的 _get 和 _set 属性上即可。

最后,在 ref 的 value 计算属性内部分别执行 this._get() 和 this._set() 即可实现功能。

3-4,shallowReactive

接口的官方文档点击这里

shallowReactive 不会对对象进行深层次的响应式转化,实现源码如下所示:

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

实现的关键在 shallowReactiveHandlers 和 shallowCollectionHandlers 中,这两个是 Proxy 的handlers。

首先看 shallowReactiveHandlers。

export const shallowReactiveHandlers = /*#__PURE__*/ extend(
  {},
  mutableHandlers,
  {
    get: shallowGet,
    set: shallowSet
  }
)
const shallowGet = /*#__PURE__*/ createGetter(false, true)

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

    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

在 createGetter 函数中,如果 shallow 属性为 true 的话,则直接返回 res,如果 shallow 属性为 false 并且 res 是一个对象的话,会执行 reactive 函数进行深层次响应式的处理。

接下来看 shallowCollectionHandlers。

export const shallowCollectionHandlers: ProxyHandler<CollectionTypes> = {
  get: /*#__PURE__*/ createInstrumentationGetter(false, true)
}
// 创建收集类数据的 Proxy get handler
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
  // instrumentations 是一个对象,这个对象的 key 是收集类数据的方法名,value 是对应的重写方法
  const instrumentations = shallow
    ? isReadonly
      ? shallowReadonlyInstrumentations
      : shallowInstrumentations
    : isReadonly
    ? readonlyInstrumentations
    : mutableInstrumentations
  // 收集类数据的代理 get handler,借助这个重写收集类数据的方法
  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
    }

    // 如果 key 是需要重写的方法名时,返回 instrumentations[key](返回重写的方法)
    return Reflect.get(
      hasOwn(instrumentations, key) && key in target
        ? instrumentations
        : target,
      key,
      receiver
    )
  }
}

这里,以 shallowInstrumentations 为例。

const shallowInstrumentations: Record<string, Function> = {
  get(this: MapTypes, key: unknown) {
    return get(this, key, false, true)
  },
  get size() {
    return size(this as unknown as IterableCollections)
  },
  has,
  add,
  set,
  delete: deleteEntry,
  clear,
  forEach: createForEach(false, true)
}
function get(
  target: MapTypes,
  key: unknown,
  isReadonly = false,
  isShallow = false
) {
  // #1772: readonly(reactive(Map)) should return readonly + reactive version
  // of the value
  target = (target as any)[ReactiveFlags.RAW]
  const rawTarget = toRaw(target)
  const rawKey = toRaw(key)
  if (!isReadonly) {
    if (key !== rawKey) {
      track(rawTarget, TrackOpTypes.GET, key)
    }
    track(rawTarget, TrackOpTypes.GET, rawKey)
  }
  const { has } = getProto(rawTarget)
  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
  if (has.call(rawTarget, key)) {
    return wrap(target.get(key))
  } else if (has.call(rawTarget, rawKey)) {
    return wrap(target.get(rawKey))
  } else if (target !== rawTarget) {
    // #3602 readonly(reactive(Map))
    // ensure that the nested reactive `Map` can do tracking for itself
    target.get(key)
  }
}

const toShallow = <T extends unknown>(value: T): T => value

export const toReactive = <T extends unknown>(value: T): T =>
  isObject(value) ? reactive(value) : value

export const toReadonly = <T extends unknown>(value: T): T =>
  isObject(value) ? readonly(value as Record<any, any>) : value

这里面主要看 const wrap 以下的代码,target.get(key) 是从收集类数据中获取到的数据,如果 isShallow 为 true 的话,wrap 函数的值指向 toShallow,我们可以发现 toShallow 函数并没有对返回值进行任何的处理,只是将参数直接返回出去。而当 isShallow 为 false 的话,wrap 的值指向 toReactive 或者 toReadonly,这两个函数会对返回值进行响应式或者只读的处理。

结合上面两点,使用 shallowReactive 函数时,内部的数据不会进行响应式的处理,所以说 shallowReactive 是浅层的。

3-5,shallowReadonly

实现原理和 shallowReactive 一样,这里就不过多赘述了。

3-6,toRaw

官方文档点击这里

当我们使用 reactive()、readonly()、shallowReactive()、shallowReadonly() 创建代理对象时,Vue 内部会将原始数据保存到代理对象的 __v_raw 属性上,所以 toRaw 的实现原理很简单,只要获取并返回代理对象的 __v_raw 属性即可,源码如下所示:

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

因为一个响应式的数据还可以被 readonly 处理,所以一个响应式数据的原始数据有可能是深层次的,所以在 toRaw 函数中,进行递归的调用进行处理这种情况。

3-7,markRaw

官方文档点击这里

markRaw 的实现原理很简单,只是做一个标记而已,源码如下所示:

export function markRaw<T extends object>(
  value: T
): T & { [RawSymbol]?: true } {
  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
  })
}

export const enum ReactiveFlags {
  SKIP = '__v_skip',
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly',
  IS_SHALLOW = '__v_isShallow',
  RAW = '__v_raw'
}

在其他的接口进行响应式处理时,如果参数上的 __v_skip 属性的值为 true,则不会进行响应式的处理。

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  ......

  const targetType = getTargetType(target)
  // 如果当前的数据类型是 INVALID 的话,直接返回 target
  if (targetType === TargetType.INVALID) {
    return target
  }
  ......
}

function getTargetType(value: Target) {
  return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
    ? TargetType.INVALID
    : targetTypeMap(toRawType(value))
}

3-8,effectScope

官方文档点击这里

effectScope 的核心思想和依赖收集是一样的,在依赖收集中,ReactiveEffect 是将自身实例挂在到全局作用域中,这样其他地方的代码都能够获取到当前激活的 ReactiveEffect 实例,然后进行依赖收集。

effectScope 的核心思想是当执行 EffectScope 实例的 run 函数时,会将当前 EffectScope 实例挂载到全局上,然后当 new ReactiveEffect 的时候,将 ReactiveEffect 实例收集存储到 EffectScope 实例中。当执行 EffectScope 实例的 stop 方法时,EffectScope 实例会遍历执行收集的 ReactiveEffect 的 stop 方法,ReactiveEffect 的 stop 方法会进行依赖的重置操作,这样就实现了 effectScope API 的功能了。

源码如下所示:

let activeEffectScope: EffectScope | undefined

export function effectScope(detached?: boolean) {
  return new EffectScope(detached)
}

export class EffectScope {
  active = true
  effects: ReactiveEffect[] = []

  constructor(detached = false) {
  }

  run<T>(fn: () => T): T | undefined {
    if (this.active) {
      const currentEffectScope = activeEffectScope
      try {
        activeEffectScope = this
        return fn()
      } finally {
        activeEffectScope = currentEffectScope
      }
    } else if (__DEV__) {
      warn(`cannot run an inactive effect scope.`)
    }
  }

  stop(fromParent?: boolean) {
    if (this.active) {
      let i, l
      for (i = 0, l = this.effects.length; i < l; i++) {
        this.effects[i].stop()
      }
      this.active = false
    }
  }
}

export function recordEffectScope(
  effect: ReactiveEffect,
  scope: EffectScope | undefined = activeEffectScope
) {
  if (scope && scope.active) {
    scope.effects.push(effect)
  }
}
export class ReactiveEffect<T = any> {
  constructor(
    public fn: () => T,
    public scheduler: EffectScheduler | null = null,
    scope?: EffectScope
  ) {
    recordEffectScope(this, scope)
  }

  stop() {
    if (this.active) {
      cleanupEffect(this)
      this.active = false
    }
  }
}

当执行 scope.run 函数时,其先将自身设置到全局变量 activeEffectScope 上,然后执行 fn 函数,fn 函数的执行会引起 ReactiveEffect 类的实例化,在 ReactiveEffect 类的构造函数中,执行了 recordEffectScope 工具函数,这个函数的作用是将 ReactiveEffect 实例收集到 EffectScope 实例的 effects 属性中。

当执行 scope.stop 方法时,内部会遍历执行 this.effects 中 ReactiveEffect 实例的 stop 方法,这个方法会重置响应式数据和 ReactiveEffect 实例的依赖收集关系。

3-9,getCurrentScope

官方文档点击这里

let activeEffectScope: EffectScope | undefined

export function getCurrentScope() {
  return activeEffectScope
}

3-10,onScopeDispose

官方文档点击这里

let activeEffectScope: EffectScope | undefined

export function onScopeDispose(fn: () => void) {
  if (activeEffectScope) {
    activeEffectScope.cleanups.push(fn)
  }
}
export class EffectScope {
  active = true
  effects: ReactiveEffect[] = []
  cleanups: (() => void)[] = []

  constructor(detached = false) {
  }

  run<T>(fn: () => T): T | undefined {
    if (this.active) {
      const currentEffectScope = activeEffectScope
      try {
        activeEffectScope = this
        return fn()
      } finally {
        activeEffectScope = currentEffectScope
      }
    } else if (__DEV__) {
      warn(`cannot run an inactive effect scope.`)
    }
  }

  stop(fromParent?: boolean) {
    if (this.active) {
      let i, l
      for (i = 0, l = this.effects.length; i < l; i++) {
        this.effects[i].stop()
      }
      for (i = 0, l = this.cleanups.length; i < l; i++) {
        this.cleanups[i]()
      }
      this.active = false
    }
  }
}

这个 API 很简单,只需要将函数参数 push 到 activeEffectScope.cleanups 数组中即可,后续执行 scope.stop 方法时,遍历执行 cleanups 数组中的函数即可。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值