Vue 3.x 源码阅读相关笔记 之 reactive 函数

Vue 版本: 3.2.x

总体功能

目前来看reactive 函数主要功能正如官网所说,通过传入的对象,创建一个响应式对象(代理Proxy)。

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, // 针对COMMON类型
    mutableCollectionHandlers, // 针对COLLECTION 类型
    reactiveMap
  )
}
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 is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  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
  }
  
  // 创建Proxy对象
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  // 进行缓存
  proxyMap.set(target, proxy)
  return proxy
}

详细描述

targetType

先对目标对象进行分类。在代码中,将target对象分为三种类型:COMMONCOLLECTIONINVALID

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
  }
}

针对不同类型,在不同的阶段,进行不同的操作。

Handlers

针对通常情况下的对象(not readonly 、not shallow),创建的Proxy对象,采用如下两种handlers

mutable 可变的

mutableHandlers

针对COMMON类型的对象,该函数对该对象的各个handler进行了定制。

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

接下来,深入到各自的Handler中进行探究。

  • getter

涉及的函数createGetter

在响应式对象的getter handler中,主要进行依赖的收集。主要代码:

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {


    const targetIsArray = isArray(target)

     // 数组特殊处理
    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)
    }
    
    // ...
    
    if (isRef(res)) {
      // ref unwrapping - does not apply for Array + integer key.
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }
    
    // 当属性为对象时,再次执行reactive
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}
  • 数组的特殊处理

涉及函数createArrayInstrumentations

数组属于特殊的对象,有自己的特性方法,需要进行特殊处理。

    - includes, indexOf, lastIndexOf方法在执行时,会对数组中元素进行访问操作,所以对数组的每个元素进行依赖收集。(只是用于应对可能的响应式需求)

    - 'push', 'pop', 'shift', 'unshift', 'splice'这些方法会改变数组长度length,而length 是数组的一个属性,也会进行依赖收集,因此在执行这些方法时,要暂停依赖收集,否则会陷入死循环。

/**
 * 对数组的 'includes', 'indexOf', 'lastIndexOf' 以及 'push', 'pop', 'shift', 'unshift', 'splice'方法加入追踪,以便进行依赖收集
 * 
 * @returns
 */
function createArrayInstrumentations() {
  const instrumentations: Record<string, Function> = {}
  // 装配对操作敏感的数组方法,以用于可能的响应式操作
  // instrument identity-sensitive Array methods to account for possible reactive
  // values
  ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
    // 这里的this是个伪参数,仅用于静态检查,属于ts的用法,参考 this参数:https://www.tslang.cn/docs/handbook/functions.html
    instrumentations[key] = function(this: unknown[], ...args: unknown[]) {
      // 转换为原始类型
      const arr = toRaw(this) as any
      // 对数组的每个元素进行追踪,为什么要在这三个方法里进行追踪?因为确实会依赖。依赖追踪是在get方法中进行的
      for (let i = 0, l = this.length; i < l; i++) {
        track(arr, TrackOpTypes.GET, i + '')
      }
      // we run the method using the original args first (which may be reactive)
      // 如果参数不是响应式的,这一步直接就会有结果
      const res = arr[key](...args)
      if (res === -1 || res === false) {
        // if that didn't work, run it again using raw values.
        // 以防参数是响应式的,再执行一次
        return arr[key](...args.map(toRaw))
      } else {
        return res
      }
    }
  })
  
  // 因为length的变化也会被侦听到,所以这些会改变数组长度的方法执行时,就不进行依赖追踪
  ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
    instrumentations[key] = function(this: unknown[], ...args: unknown[]) {
      pauseTracking()
      // 是因为此处的操作如果追踪的话,会死循环
      const res = (toRaw(this) as any)[key].apply(this, args)
      resetTracking()
      return res
    }
  })
  return instrumentations
}
  • setter

涉及函数createSetter

该handler的主要作用是更新派发(当值发生变化时,通知相应的依赖)

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    let oldValue = (target as any)[key]
    if (!shallow) {
      // 转化为原始值
      value = toRaw(value)
      oldValue = toRaw(oldValue)
      // 这一步直接赋值,交给Ref对象内部的响应式机制处理
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value
        return true
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }

    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    // 只触发当前对象,不管原型链上的target
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
        // 如果有这个key,并且发生了改变,才执行trigger
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}
  • deleteProperty

删除操作会触发执行的handler,拦截删除操作。

该函数同样会执行相应的trigger。

function deleteProperty(target: object, key: string | symbol): boolean {
  const hadKey = hasOwn(target, key)
  const oldValue = (target as any)[key]
  const result = Reflect.deleteProperty(target, key)
  if (result && hadKey) {
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
  }
  return result
}
  • has

该函数是针对 in 操作符的代理方法。

进行了访问操作,则会进行相应的依赖收集。

function has(target: object, key: string | symbol): boolean {
  const result = Reflect.has(target, key)
  if (!isSymbol(key) || !builtInSymbols.has(key)) {
    track(target, TrackOpTypes.HAS, key)
  }
  return result
}
  • ownKeys

用于拦截Reflect.ownKeys方法,但执行Object.keys方法时,也会被拦截(入参为新的代理对象)

这里只干了一件事,就是进行依赖收集。

function ownKeys(target: object): (string | symbol)[] {
  track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
  return Reflect.ownKeys(target)
}

mutableCollectionHandlers

针对COLLECTION类型的对象,创建Proxy对象所使用的Handlers为:

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

创建COLLECTION类型的getter

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
    }
    
    // 如果是集合的其他方法,执行instrumentations中定义的方法
    return Reflect.get(
      hasOwn(instrumentations, key) && key in target
        ? instrumentations
        : target,
      key,
      receiver
    )
  }
}
  • mutableInstrumentations

COLLECTION类型的各个方法进行重写,以应对可能的响应式需求

  const mutableInstrumentations: Record<string, Function> = {
    get(this: MapTypes, key: unknown) {
      return get(this, key)
    },
    get size() {
      return size(this as unknown as IterableCollections)
    },
    has,
    add,
    set,
    delete: deleteEntry,
    clear,
    forEach: createForEach(false, false)
  }
  • get(key)
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)
  // 不管key 和 rawKey是否一样,都能保证被依赖收集
  if (key !== rawKey) {
    !isReadonly && track(rawTarget, TrackOpTypes.GET, key)
  }
  !isReadonly && 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
    // 如果 原始对象上不存在key或者 rawKey ,则直接取值返回。
    target.get(key)
  }
}
  • get size()
function size(target: IterableCollections, isReadonly = false) {
  target = (target as any)[ReactiveFlags.RAW]
  !isReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
  return Reflect.get(target, 'size', target)
}
  • has
function has(this: CollectionTypes, key: unknown, isReadonly = false): boolean {
  const target = (this as any)[ReactiveFlags.RAW]
  const rawTarget = toRaw(target)
  const rawKey = toRaw(key)
  // 这里的判断方式跟get一致,只是传入的类型由 GET变为 HAS
  if (key !== rawKey) {
    !isReadonly && track(rawTarget, TrackOpTypes.HAS, key)
  }
  !isReadonly && track(rawTarget, TrackOpTypes.HAS, rawKey)
  return key === rawKey
    ? target.has(key)
    : target.has(key) || target.has(rawKey)
}
  • add
function add(this: SetTypes, value: unknown) {
  value = toRaw(value)
  const target = toRaw(this)
  const proto = getProto(target)
  const hadKey = proto.has.call(target, value)
  if (!hadKey) {
    target.add(value)
    // 派发ADD类型的变更
    trigger(target, TriggerOpTypes.ADD, value, value)
  }
  return this
}
  • set
function set(this: MapTypes, key: unknown, value: unknown) {
  value = toRaw(value)
  const target = toRaw(this)
  const { has, get } = getProto(target)

  // 检测key 是否存在于target中
  let hadKey = has.call(target, key)
  if (!hadKey) {
    key = toRaw(key)
    hadKey = has.call(target, key)
  } else if (__DEV__) {
    checkIdentityKeys(target, has, key)
  }

  const oldValue = get.call(target, key)
  target.set(key, value)
  // 更新派发至对应依赖项中
  if (!hadKey) {
    trigger(target, TriggerOpTypes.ADD, key, value)
  } else if (hasChanged(value, oldValue)) {
    trigger(target, TriggerOpTypes.SET, key, value, oldValue)
  }
  return this
}
  • deleteEntry
function deleteEntry(this: CollectionTypes, key: unknown) {
  const target = toRaw(this)
  const { has, get } = getProto(target)
  let hadKey = has.call(target, key)
    // 检测key 是否存在于target中
  if (!hadKey) {
    key = toRaw(key)
    hadKey = has.call(target, key)
  } else if (__DEV__) {
    checkIdentityKeys(target, has, key)
  }

  const oldValue = get ? get.call(target, key) : undefined
  // forward the operation before queueing reactions
  // 更新派发至对应依赖项中
  const result = target.delete(key)
  if (hadKey) {
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
  }
  return result
}
  • clear()
function clear(this: IterableCollections) {
  const target = toRaw(this)
  const hadItems = target.size !== 0
  const oldTarget = __DEV__
    ? isMap(target)
      ? new Map(target)
      : new Set(target)
    : undefined
  // forward the operation before queueing reactions
  // 清空操作将更新派发至对应依赖中
  const result = target.clear()
  if (hadItems) {
    trigger(target, TriggerOpTypes.CLEAR, undefined, undefined, oldTarget)
  }
  return result
}
  • forEach
function createForEach(isReadonly: boolean, isShallow: boolean) {
  return function forEach(
    this: IterableCollections,
    callback: Function,
    thisArg?: unknown
  ) {
    const observed = this as any
    const target = observed[ReactiveFlags.RAW]
    const rawTarget = toRaw(target)
    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
    // 如果非只读,进行依赖收集
    !isReadonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
    return target.forEach((value: unknown, key: unknown) => {
      // important: make sure the callback is
      // 1. invoked with the reactive map as `this` and 3rd arg
      // 2. the value received should be a corresponding reactive/readonly.
      return callback.call(thisArg, wrap(value), wrap(key), observed)
    })
  }
}

track()

涉及函数track

该函数主要负责依赖收集,每个对象有自己的depsMap ,存储于 targetMap (全局变量)中,用于存储收集到的依赖。而在depsMap 中,每个属性又有着自己的depsSet,用来存储各属性收集到的依赖。借助一张图来解释:

export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (!isTracking()) {
    return
  }
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = createDep()))
  }

  const eventInfo = __DEV__
    ? { effect: activeEffect, target, type, key }
    : undefined

  // TODO 3.2新增,主要服务于effectScope作用域API
  trackEffects(dep, eventInfo)
}

trigger()

涉及函数triggertriggerEffects

trigger:根据对应情况,取出需要触发执行的 ReactiveEffect

export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }

  let deps: (Dep | undefined)[] = []

  // 如果是清空操作
  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared
    // trigger all effects for target
    // 取出depsMap中的所有依赖
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {
    // 如果变化的是长度
    depsMap.forEach((dep, key) => {
      // 依赖中大于新值则进行追加
      if (key === 'length' || key >= (newValue as number)) {
        deps.push(dep)
      }
    })
  } else {
    // (稍后运行的相关操作)schedule runs for SET | ADD | DELETE
    // key !== undefined
    if (key !== void 0) {
      deps.push(depsMap.get(key))
    }

    // (对特定对象的iteration key 也需要进行派发)also run for iteration key on ADD | DELETE | Map.SET
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          deps.push(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        // 针对非数组对象和Map特殊处理
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        // 这里为什么没对数组处理呢?因为对数组的属性进行删除操作,并不会改变length属性
        break
      case TriggerOpTypes.SET:
        // 针对Map类型特殊处理
        if (isMap(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  const eventInfo = __DEV__
    ? { target, type, key, newValue, oldValue, oldTarget }
    : undefined

  if (deps.length === 1) {
    if (deps[0]) {
      if (__DEV__) {
        triggerEffects(deps[0], eventInfo)
      } else {
        triggerEffects(deps[0])
      }
    }
  } else {
    const effects: ReactiveEffect[] = []
    for (const dep of deps) {
      if (dep) {
        // dep为数组
        effects.push(...dep)
      }
    }
    if (__DEV__) {
      triggerEffects(createDep(effects), eventInfo)
    } else {
      triggerEffects(createDep(effects))
    }
  }
}

triggerEffects:将Dep类型的Set 转化为数组,逐个触发effect对应的回调。

export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // spread into array for stabilization
  for (const effect of isArray(dep) ? dep : [...dep]) {
    if (effect !== activeEffect || effect.allowRecurse) {
      if (__DEV__ && effect.onTrigger) {
        effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
      }
      if (effect.scheduler) {
        effect.scheduler()
      } else {
        effect.run()
      }
    }
  }
}

总结

  • 最终目的,就是在取值(get)操作中进行依赖收集,在值有所变化(增、删、改)时,进行更新派发,实现响应式。

其他

解释:

由于该函数最终会将传入的参数包装成Proxy对象,而Proxy对象的入参就要求只能是对象。另一方面,从createReactiveObject函数中,我们看到target能够支持的类型如下所示:

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
  }
}

因此,限定reactive函数的入参只能是对象。

参考

这个版本已经过时了,但仍然具有参考意义
https://zhuanlan.zhihu.com/p/148423054
https://zhuanlan.zhihu.com/p/306540786

原文地址:https://www.cnblogs.com/ForRickHuan/p/15493687.html