关于vue.js:vue3响应式分析

53次阅读

共计 10747 个字符,预计需要花费 27 分钟才能阅读完成。

vue3 响应式剖析

  1. 首先对 vue3 响应式解析之前,须要前置常识 ProxyReflect有所理解,,对于这两个常识我举荐看阮一峰老师的 ES6 入门教程
  2. vue3 的响应式我是以 reactive 为入口进行梳理,流程如下图
  3. 源码地位:reactivity/src/...,分四局部解析

    • reactive 文件:指标对象转化为 proxy 实例
    • baseHandler 文件:根本类型处理器
    • collectionHandlers 文件:Map、Set 处理器
    • effect 文件:收集触发依赖
  4. 如果不想看源码解析,能够间接看总结🐶

reactive

  • reactive:将一个 JS 对象转为具备响应式的 proxy 实例

    export function reactive(target: object) {
      // 如果是只读数据,就间接返回
      if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {return target}
      return createReactiveObject(
        target,
        false,
        mutableHandlers,
        mutableCollectionHandlers,
        reactiveMap
      )
    }
  • createReactiveObject

    function createReactiveObject(
      target: Target, // 源对象
      isReadonly: boolean, // 是否只读
      baseHandlers: ProxyHandler<any>, // 根本类型的 handlers
      collectionHandlers: ProxyHandler<any>, // 次要针对 (set、map、weakSet、weakMap) 的 handlers
      proxyMap: WeakMap<Target, any>
    ) {
    // 如果不是一个对象,间接返回,并且在开发环境收回正告
      if (!isObject(target)) {if (__DEV__) {console.warn(`value cannot be made reactive: ${String(target)}`)
        }
        return target
      }
      // 如果曾经是响应式,间接返回
      if (target[ReactiveFlags.RAW] &&
        !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
      ) {return target}
    
      // 如果指标对象曾经存在代理,间接返回
      const existingProxy = proxyMap.get(target)
      if (existingProxy) {return existingProxy}
    
      // 如果类型值不是 Object、Array、Map、Set、WeakMap、WeakSet 的,间接返回
      const targetType = getTargetType(target)
      if (targetType === TargetType.INVALID) {return target}
    
      // 依据不同的类型值赋予不同的 handlers,就是我之前图上画的离开解决
      /* 
        把 set、Map 这种数据与根底数据离开解决,是因为 Map、Set 中存储的数据必须通过 this 进行拜访
        然而被 proxy 劫持后,this 就变成了 proxy,所以须要非凡解决,把劫持办法进行重写
       */
      const proxy = new Proxy(
        target,
        targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
      )
      proxyMap.set(target, proxy)
      return proxy
    }

baseHandler

baseHandler次要剖析 reactive 的处理器对象mutableHandlers

// 对 get set delete has onwKeys 做了拦挡解决
export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}

get

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 &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
            ? shallowReactiveMap
            : reactiveMap
        ).get(target)
    ) {return target}

    const targetIsArray = isArray(target)

    // 如果 target 是数组并且 key 属于一些数组的原始办法,即触发拦挡
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {return Reflect.get(arrayInstrumentations, key, receiver)
    }

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

    // 如果 key 是 symbol 的内置办法,或者是原型对象,间接返回
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {return res}

    // 只读对象不收集依赖,因为不会触发依赖更新
    if (!isReadonly) {track(target, TrackOpTypes.GET, key)
    }

    // 浅层响应立刻返回,不递归转化
    if (shallow) {return res}

    // 如果是 ref 对象(数组除外),返回真正的值,if (isRef(res)) {const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }

    if (isObject(res)) {// 因为 proxy 只能代理一层,所以 target[key]的值如果是对象,就持续对其进行代理
      return isReadonly ? readonly(res) : reactive(res)
    }
    return res
  }
}

数组办法拦挡: 对数组的两种原生办法进行了拦挡

  • 遍历查找的办法:includes、indexOf、lastIndexOf
  • 扭转数组长度的办法:push、pop、shift、unshift、splice

    function createArrayInstrumentations() {const instrumentations: Record<string, Function> = {}
      ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {const method = Array.prototype[key] as any
        instrumentations[key] = function(this: unknown[], ...args: unknown[]) {
          // 这一步是为了取原始实例,因为以后的 this 是 receiver
          const arr = toRaw(this)
          // 收集依赖
          for (let i = 0, l = this.length; i < l; i++) {track(arr, TrackOpTypes.GET, i + '')
          }
          // 触发办法,如果没有找到对应的值,就取原始值再遍历
          const res = method.apply(arr, args)
          if (res === -1 || res === false) {return method.apply(arr, args.map(toRaw))
          } else {return res}
        }
      })
      /* 
        因为扭转数组长度的办法,执行期间会触发 length 的 get 和 set
        就回导致有限循环 track 和 trigger
        所以就用 pauseTracking()禁用依赖收集,触发办法后,再用 resetTracking()复原 track
       */
      ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {const method = Array.prototype[key] as any
        instrumentations[key] = function(this: unknown[], ...args: unknown[]) {pauseTracking()
          const res = method.apply(this, args)
          resetTracking()
          return res
        }
      })
      return instrumentations
    }

set

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,但新的值不是,则将新的值赋给 oldValue.value
      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)
    
    // 判断 receiver 是以后对象的 proxy 实例,避免原型链上的 proxy 触发更新
    if (target === toRaw(receiver)) {
      // 判断新增属性还是批改属性
      if (!hadKey) {trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

deleteProperty、has…

  • deleteProperty、has、ownKeys 的源码就不贴了,都是判断 key 的属性,而后抉择触发或者收集依赖

collectionHandlers

  • collection 次要剖析 reactive 的重写办法对象 mutableInstrumentations

    // 次要对以下原生 api 进行了改写
    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

    function get(
      target: MapTypes,
      key: unknown,
      isReadonly = false,
      isShallow = false
    ) {
      // 获取原始值从新赋值给 target
      target = (target as any)[ReactiveFlags.RAW]
    
      // 对 target 源对象和 key 进一步获取原始值
      const rawTarget = toRaw(target)
      const rawKey = toRaw(key)
    
      // 以后 key 和原始 key 均进行依赖收集(track)
      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
      /* 
        如果源对象有 key 对应的属性,就通过原生 get 办法取到值,并对该值进行响应式转换,返回转换后的响应式对象,如果没有,就去 key 原始值中去查找
      */
      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) {target.get(key)
      }
    }

size

// 对 size 属性做 get 拦挡
function size(target: IterableCollections, isReadonly = false) {target = (target as any)[ReactiveFlags.RAW]
  // 获取 size 和获取数组的 length 相似,都用专门的 key 做依赖收集
  !isReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
  return Reflect.get(target, 'size', target)
}

set

function set(this: MapTypes, key: unknown, value: unknown) {
  // 获取 value 和 this 上下文的原始值
  value = toRaw(value)
  const target = toRaw(this)
  const {has, get} = getProto(target)

  /* 
    判断源对象是否曾经存在对应的 key
    1. 首先查找源对象是否已有 key 对应的属性
    2. 如果没有,再查找 key 对应的原始值在源对象的属性是否存在
  */
  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)

  // 触发依赖,新增属性和批改属性离开进行 trigger
  if (!hadKey) {trigger(target, TriggerOpTypes.ADD, key, value)
  } else if (hasChanged(value, oldValue)) {trigger(target, TriggerOpTypes.SET, key, value, oldValue)
  }
  return this
}

has、clear…

  • 其余重写办法我就不上代码了,不同点是单个属性触发单个的依赖,如果是遍历所有属性的办法就触发所有依赖

effect

  • effect 文件作为响应式的外围,次要负责收集依赖,触发依赖

effect

  • effect 函数次要是生成收集依赖所需的依赖函数

    export function effect<T = any>(fn: () => T,
      options: ReactiveEffectOptions = EMPTY_OBJ
    ): ReactiveEffect<T> {
      // 如果曾经是 effect 函数,获得原来的 fn
      if (isEffect(fn)) {fn = fn.raw}
      const effect = createReactiveEffect(fn, options)
    
      // 如果 lazy 为 false,立刻执行一次
      if (!options.lazy) {effect()
      }
      return effect
    }
  • createReactiveEffect:生成 effect 对象

    function createReactiveEffect<T = any>(fn: () => T,
      options: ReactiveEffectOptions
    ): ReactiveEffect<T> {const effect = function reactiveEffect(): unknown {
        // 没有激活,曾经调用 stop 函数进行监听
        if (!effect.active) {return fn()
        }
        /**
         * effectStack 是一个全局的 effect 栈构造
         * 设计为栈构造是因为如果 effect 是嵌套时,为了避免内层副作用函数笼罩外层副作用函数,在收集时只收集栈顶的,这样就不会收集到谬误的副作用函数
         */
        if (!effectStack.includes(effect)) {
          /**
           * 为了保障以后 effect 的 dep 是最新,因为在一些判断解决中,可能会导致一些有效的副作用函数
           * 所以为了勾销这些不必要的更新,就要革除 effect 依赖
           */
          cleanup(effect)
          try {enableTracking() // 从新收集依赖
            effectStack.push(effect)
            activeEffect = effect
            return fn()} finally {
            /* 
              track 将依赖函数 activeEffect 增加到对应的 dep 中,而后将 activeEffect 重置为上一个 effect 的值
            */
            effectStack.pop()
            resetTracking()
            activeEffect = effectStack[effectStack.length - 1]
          }
        }
      } as ReactiveEffect
      effect.id = uid++ // 自增 ID
      effect.allowRecurse = !!options.allowRecurse // 递归状态
      effect._isEffect = true // 用于标识办法是不是 effect
      effect.active = true // 用于判断以后 effect 是否激活,有一个 stop()来将它设为 false
      effect.raw = fn // effect 的执行函数
      effect.deps = [] // 用于收集依赖
      effect.options = options // 创立 effect 传入的 options
      return effect
    }
  • activeEffect 就是标记 track 所需的依赖函数

track

  • track 就是 baseHandler 和 collectionHandlers 文件中频繁应用的收集依赖函数
  • 首先须要看一个要害变量 targetMap

    // targetMap 是依赖管理中心,收集依赖和触发依赖都依靠于这个 Map 数据
    // 上面是 targetMap 的定义(target -> key -> dep)
    // target: 监听的对象源
    // key: 监听的键值
    // dep:依赖函数
    type Dep = Set<ReactiveEffect>
    type KeyToDepMap = Map<any, Dep>
    const targetMap = new WeakMap<any, KeyToDepMap>()
    // 格局大抵为
    targetMap = {
    target: {key1: { fn1, fn2}
      key2: {fn1, fn2}
    }
    }
    /**
     * @param {target} 指标对象
     * @param {type} 收集类型
     * @param {key} 须要收集依赖的 key
     */
    export function track(target: object, type: TrackOpTypes, key: unknown) {
      // activeEffect 为空,就示意以后没有依赖,就没必要做依赖收集了
      if (!shouldTrack || activeEffect === undefined) {return}
      // 获取以后依赖数据
      let depsMap = targetMap.get(target)
    
      if (!depsMap) {targetMap.set(target, (depsMap = new Map()))
      }
    
      // 如果以后数据中没有所属的依赖 key,就从新设置一个
      let dep = depsMap.get(key)
      if (!dep) {depsMap.set(key, (dep = new Set()))
      }
      // 增加依赖函数
      if (!dep.has(activeEffect)) {dep.add(activeEffect)
        activeEffect.deps.push(dep)
        if (__DEV__ && activeEffect.options.onTrack) {
          activeEffect.options.onTrack({
            effect: activeEffect,
            target,
            type,
            key
          })
        }
      }
    }

trigger

// 触发依赖
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) {return}

  const effects = new Set<ReactiveEffect>()
  const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {if (effectsToAdd) {
      effectsToAdd.forEach(effect => {// 防止循环触发依赖 相似 `effect(() => obj.foo++)`
        if (effect !== activeEffect || effect.allowRecurse) {effects.add(effect)
        }
      })
    }
  }

  if (type === TriggerOpTypes.CLEAR) {
    // 在清空前,将对应的依赖全副增加到部分 Set
    depsMap.forEach(add)
  } else if (key === 'length' && isArray(target)) {
    // 当数组的 length 属性变动时触发
    depsMap.forEach((dep, key) => {if (key === 'length' || key >= (newValue as number)) {add(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    // 往相应队列增加依赖
    if (key !== void 0) {add(depsMap.get(key))
    }

    // also run for iteration key on ADD | DELETE | Map.SET
    // 通过不同的 TriggerOpTypes 将 depsMap 的数据取出,增加到 effects
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {add(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          add(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {add(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {add(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  const run = (effect: ReactiveEffect) => {if (__DEV__ && effect.options.onTrigger) {
      effect.options.onTrigger({
        effect,
        target,
        key,
        type,
        newValue,
        oldValue,
        oldTarget
      })
    }
    if (effect.options.scheduler) {
      // 如果有调度属性,就通过 scheduler 解决执行
      effect.options.scheduler(effect)
    } else {effect()
    }
  }

  effects.forEach(run)
}

总结

  • vue3 响应式是通过数据劫持和公布订阅的模式进行解决,首先 vue3 中的数据是通过 proxy 做了一层代理,而后解决 proxyhandler,根本类型的 handler 是通过 baseHandlers,非凡类型(map,set) 的handler是通过collectHandlers
  • handler中获取属性的操作通过 track 进行依赖收集,批改属性的操作通过 trigger 进行依赖触发,依赖的收集与触发是通过依赖管理中心 targetMap 保留的
  • track进行收集时,他收集的是 activeEffect,这个变量存储的就是正在触发的副作用函数,activeEffect 通过 effect() 办法进行收集
  • effect()罕用的三个中央

    • 组件副作用函数
    • watch
    • computed

正文完
 0