• 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

  • 相关阅读:
    Chrome浏览器与常用插件推荐
    时间戳 转换24小时制
    fis3 开启相对地址
    web手机端禁止滑动,web手机端禁止上下滑动。
    rem的用法
    手机端复制,pc端复制
    ruby 镜像安装
    使用Potree渲染大规模点云-踩坑记录
    移动端真机调试神器-spy-debugger
    手撕Promise.any
  • 原文地址:https://www.cnblogs.com/ForRickHuan/p/15493687.html
Copyright © 2020-2023  润新知