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