Vue 3.X 依赖收集与派发通知

202 阅读3分钟

reactive Get

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

    ...
    指定特殊key值的响应
    ...
    
    ...
    对于Array与Symbol的的处理
    ...

    // 依赖收集(后面细说)
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }
    
    // 深层响应式转换,shallowReactive与shallowReadonly
    if (shallow) {
      return res
    }

    // 如果访问的参数是ref,返回res.value
    // 这也就是
    // const count = ref(1)
    // const obj = reactive({})

    // obj.count = count
    // obj.count === count.value
    //的原因
    if (isRef(res)) {
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }

    // res 是个对象或者数组类型,则递归执行 reactive 函数把 res 变成响应式
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

指定特殊key值

    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (
      key === ReactiveFlags.RAW &&
      receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
    ) {
      return target
    }

IS_REACTIVEIS_READONLY,判断是 reactive 还是 readonly 响应 RAW,从对应全局Map中的获取对应的reactive或readonly

对于Array与Symbol的的处理

    const targetIsArray = isArray(target)
    
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      //对提前指定的key值进行了处理
      //'includes', 'indexOf', 'lastIndexOf'
      //'push', 'pop', 'shift', 'unshift', 'splice'
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
    
    //获取值
    const res = Reflect.get(target, key, receiver)
    
    //内置 Symbol key 不需要依赖收集
    if (
      isSymbol(key)
        ? builtInSymbols.has(key as symbol)
        : isNonTrackableKeys(key)
    ) {
      return res
    }

arrayInstrumentations

const arrayInstrumentations: Record<string, Function> = {}

;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
  const method = Array.prototype[key] as any
  arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) {
  
    //看上面指定特殊key值,可得arr就是得到的是传入reactive的target
    //toRaw=(observed) => (observed && toRaw((observed as Target)[ReactiveFlags.RAW])) || observed
    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
    }
  }
})

//直接执行返回结果就行,这里的变化可以被proxy监听到就无需想vue2.x中的收集依赖了
;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
  const method = Array.prototype[key] as any
  arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) {
    const res = method.apply(this, args)
    return res
  }
})

可以看到在includes, indexOf, lastIndexOf中做了除了调用代理函数外,还做了依赖收集

reactive Set

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    //获取oldValue
    let oldValue = (target as any)[key]
    
    //这里如果oldValue是ref,但value不是一个ref。赋值给oldValue.value
    if (!shallow) {
      value = toRaw(value)
      oldValue = toRaw(oldValue)
      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
    }

    //target是数组情况下,Number(key)是否小于target的长度
    //target是对象情况下,key是否存在target中
    //hadKey:key是否存在target中
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    
    // trigger 函数派发通知
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        //key不存在target中,使用ADD标记
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        //key存在target中,使用SET标记
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

reactive中ref元素的修改

    const a = ref(1);
    const b = reactive({a});
    b.a = 2;
    console.log(a.value, b.a); => 2 2
    a.vlaue = 3;
    console.log(a.value, b.a); => 3 3
    b.a = ref(233);
    console.log(a.value, b.a);=> 2 233
    
    value = toRaw(value)
    oldValue = toRaw(oldValue)
    if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
      oldValue.value = value
      return true
    }

oldValue是ref,value不是ref。对oldValue的value赋值,也就能确保同时修改ref

track

const a = reactive({ b : 1 })
console.log(a.b)

//target:a
//key:b
//type:'get'
function track(target: object, type: TrackOpTypes, key: unknown) {
  if (!shouldTrack || activeEffect === undefined) {
    return
  }
  
  // targetMap是全局变量,所有的 target 都在其中
  // 查询a是否在targetMap中
  
// 是否应该收集依赖
let shouldTrack = true
// 当前激活的 effect
let activeEffect
// 原始数据对象 map
const targetMap = new WeakMap()
  // depsMap用于保存a的key值,一个Map
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  
  //dep用于保存使用这个key的dep依赖,一个Set确保唯一值
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  
  if (!dep.has(activeEffect)) {
    // 收集当前激活的 effect 作为依赖
    dep.add(activeEffect)
    // 当前激活的 effect 收集 dep 集合作为依赖
    activeEffect.deps.push(dep)
  }
}

graph TD
targetMap --> depsMap --> dep
a --> b --> b的deps

元素触发get时收集依赖,同时通知已经收集的dep集合

target

export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  //track依赖收集时获得的target,没有就不用触发派发事件
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    return
  }

  //运行的 effects 集合
  const effects = new Set<ReactiveEffect>()
  const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
    if (effectsToAdd) {
      effectsToAdd.forEach(effect => {
        if (effect !== activeEffect || effect.allowRecurse) {
          effects.add(effect)
        }
      })
    }
  }

  //收集需要通知的依赖
  if (type === TriggerOpTypes.CLEAR) {
    //clear清除操作
    //搜集target下所有key的dep
    depsMap.forEach(add)
  } else if (key === 'length' && isArray(target)) {
    //数组的length值修改
    //搜集length和key值大于newValue的dep
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= (newValue as number)) {
        add(dep)
      }
    })
  } else {
    //key不为undefined
    //收集指定 key 的dep
    if (key !== void 0) {
      add(depsMap.get(key))
    }
  }
  
  //执行搜集到的所有 dep
  const run = (effect: ReactiveEffect) => {
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }

  effects.forEach(run)
}

target有两步。1,根据操作搜集对应的依赖 2,触发搜集到的依赖

tracktarget
搜集依赖派送触发
targer[targetMap] => targetMap[key] => depSet根据操作搜集depSet,统一触发

下一章将副作用函数effect