vue3.2 reactivity 之 effect, dep 源码解析

1,180 阅读5分钟

在代码块中我会添加注释,方便大家理解,配合vue文档看效果更佳 本篇针对 /packages/reactivity/src/effect.ts 与 /packages/reactivity/src/dep.ts

-------------- dep API -------------


createDep

//创建dep
export const createDep = (effects?: ReactiveEffect[]): Dep => {
  const dep = new Set<ReactiveEffect>(effects) as Dep
  dep.w = 0  // wasTracked
  dep.n = 0  // newTracked
  return dep
}

可以看到dep就是Set对象,并且该对象上有一个w标记,一个n标记。w用来表示以前是否被收集过,n表示是否是重新收集。注意这两标记用二进制表示,用于与trackOpBit做位运算。


wasTracked

//判断是否以前被收集过
export const wasTracked = (dep: Dep): boolean => (dep.w & trackOpBit) > 0

与trackOpBit与运算,例如: 0&10 = 0, 10&10 = 10, 10&100 = 0


newTracked

//判断是否是重新收集
export const newTracked = (dep: Dep): boolean => (dep.n & trackOpBit) > 0

与trackOpBit与运算,例如: 0&10 = 0, 10&10 = 10, 10&100 = 0


initDepMarkers

//初始化副作用所绑定的每个dep 的 w 标记
export const initDepMarkers = ({ deps }: ReactiveEffect) => {
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].w |= trackOpBit //或运算,例如 0|10 = 10, 10|100 = 100
    }
  }
}

finalizeDepMarkers

//对当前副作用所有绑定的dep,进行对当前副作用的判断清理,以及标记回归
export const finalizeDepMarkers = (effect: ReactiveEffect) => {
  const { deps } = effect
  if (deps.length) {
    let ptr = 0
    for (let i = 0; i < deps.length; i++) {
      const dep = deps[i]
      if (wasTracked(dep) && !newTracked(dep)) {
        dep.delete(effect)
      } else {
        deps[ptr++] = dep
      }
      //源码中这里备注的是clear bits,我觉得用标记回归去理解会比较好
      dep.w &= ~trackOpBit
      dep.n &= ~trackOpBit
    }
    deps.length = ptr
  }
}

遍历副作用中所有相关的dep,进行判断,如果以前被收集过且不是重新收集则清除。 标记回归:把w和n标记重置到effect执行前的状态。


-------------- effect API -------------

先介绍一下会用到的东西

//存储收集的依赖,使用WeakMap弱引用,当key失去引用时,可以被垃圾回收机制回收。
const targetMap = new WeakMap<any, KeyToDepMap>()
//当前递归跟踪的层数。
let effectTrackDepth = 0
// 当前递归跟踪的层数(二进制), 例如: 1, 10, 100 ...
export let trackOpBit = 1
// 最多支持 30 层递归
const maxMarkerBits = 30
//副作用栈,记录当前操作过程的所有的effect
const effectStack: ReactiveEffect[] = []
//当前副作用
let activeEffect: ReactiveEffect | undefined
//迭代key
export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '')
//Map迭代key
export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '')

shouldTrack

let shouldTrack = true  //代表能否进行依赖收集,默认为true
const trackStack: boolean[] = []  //记录shouldTrack的栈
//开启,压栈,并设置为true
export function enableTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = true
}
//暂停,压栈,并设置为false
export function pauseTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = false
}
//重置收集,出栈,并设置为出栈结果
export function resetTracking() {
  const last = trackStack.pop()
  shouldTrack = last === undefined ? true : last  //不存在则设置为true
}
//是否可收集
export function isTracking() {
  return shouldTrack && activeEffect !== undefined  //当前处理中的副作用必须有效
}

ReactiveEffect

// 响应式副作用类声明
export class ReactiveEffect<T = any> {
  active = true   //活动状态,默认true
  deps: Dep[] = [] // 存储dep的数组

  computed?: boolean    // computed?
  allowRecurse?: boolean //允许递归?
  onStop?: () => void
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void

  constructor(
    public fn: () => T,    //副作用函数
    public scheduler: EffectScheduler | null = null,	//调度器, computed 和 watch 中被使用
    scope?: EffectScope | null	// 作用范围 render 中被使用
  ) {
    recordEffectScope(this, scope) //记录副作用范围
  }

  run() {
    //如果非活动状态
    if (!this.active) {
      return this.fn()
    }
    //判断是否在副作用栈,存在则跳过
    if (!effectStack.includes(this)) {
      try {
        //把当前处理的副作用压入栈中
        effectStack.push((activeEffect = this))
        //开启收集
        enableTracking()
        //effectTrackDepth + 1 , trackOpBit进位, 例如:1<<1 = 10, 1<<2 = 100
        trackOpBit = 1 << ++effectTrackDepth
        //判断是否超过最大层数
        if (effectTrackDepth <= maxMarkerBits) {
          initDepMarkers(this) //初始化 dep.w 标记
        } else {
          cleanupEffect(this) //解除副作用与dep之间的绑定
        }
        return this.fn() //执行fn并返回
      } finally {
        //判断是否超过最大层数
        if (effectTrackDepth <= maxMarkerBits) {
          finalizeDepMarkers(this)  //去除不相关依赖,回归 n,w 标记
        }
        //effectTrackDepth - 1 , trackOpBit退位, 例如:1<<2 = 100,1<<1 = 10, 
        trackOpBit = 1 << --effectTrackDepth
        //重置收集依赖状态
        resetTracking()
        //当前副作用出栈
        effectStack.pop()
        //如果effectStack里还有副作用,则将activeEffect指向栈中顶部的副作用
        const n = effectStack.length
        activeEffect = n > 0 ? effectStack[n - 1] : undefined
      }
    }
  }
  //停止活动
  stop() {
    if (this.active) {
      cleanupEffect(this)  //清除相关依赖
      if (this.onStop) {
        this.onStop()
      }
      this.active = false
    }
  }
}

run方法: 层级积累, 初始化标记, 跑fn, 标记回归, 层级回退


cleanupEffect

//解除副作用与dep之间的绑定
function cleanupEffect(effect: ReactiveEffect) {
  const { deps } = effect
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)  //执行delete,删除 dep 中当前的副作用
    }
    deps.length = 0  // 将副作用所关联的dep数组清空
  }
}

遍历副作用中所有的dep,清楚绑定,并将deps清空。


effect

副作用函数

export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner {
  //如果fn曾经被effect过,则取出原来的fn替换之
  if ((fn as ReactiveEffectRunner).effect) {
    fn = (fn as ReactiveEffectRunner).effect.fn
  }

  const _effect = new ReactiveEffect(fn) //创建响应式副作用对象
  //如果有配置信息
  if (options) {
    extend(_effect, options) //对_effect进行配置合并
    if (options.scope) recordEffectScope(_effect, options.scope) //记录副作用范围
  }
  //如果是懒副作用则不立即执行
  if (!options || !options.lazy) {
    _effect.run()
  }
  //获取run方法,并包装对应的副作用
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect
  return runner
}

根据fn创建ReactiveEffect对象,非lazy则立即执行ReactiveEffect.run(),包装run并返回。


stop

结束副作用

export function stop(runner: ReactiveEffectRunner) {
  runner.effect.stop()
}

就调用了ReactiveEffect的stop方法。上面有具体介绍。这里只是暴露该API方便使用。


track

export function track(target: object, type: TrackOpTypes, key: unknown) {
  //当前处于不可收集状态则退出
  if (!isTracking()) {
    return
  }
  //获取target对应的dep,这里做了一些取值的逻辑处理
  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
  //收集依赖
  trackEffects(dep, eventInfo)
}

收集,这个在baseHandlers与conllectionHandlers中被使用。这里主要是对targetMap进行一些取值的逻辑处理。 在执行trackEffects前,处理好target对应的dep,并传入。


trackEffects

收集副作用

export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  let shouldTrack = false //定义自己的shouldTrack
  if (effectTrackDepth <= maxMarkerBits) {
     //如果不是重新收集
    if (!newTracked(dep)) {
      dep.n |= trackOpBit // 设置n标记,例如:0|10 = 10, 10|100 = 110
      shouldTrack = !wasTracked(dep) //以以前是否被收集过作为判读
    }
  } else {
    //依赖收集层数超出边界则直接以dep是否含有当前副作用作为判断
    shouldTrack = !dep.has(activeEffect!)
  }
  //如果需要收集,则进行 dep 与 副作用的双向关联
  if (shouldTrack) {
    dep.add(activeEffect!)
    activeEffect!.deps.push(dep)
    if (__DEV__ && activeEffect!.onTrack) {
      activeEffect!.onTrack(
        Object.assign(
          {
            effect: activeEffect!
          },
          debuggerEventExtraInfo
        )
      )
    }
  }
}

首先判断是不是重新的收集,不是则记录n标记,并判断以前是否被收集过来给shouldTrack赋值。如果需要收集则进行双向关联。dep.add(activeEffect!),触发依赖时会遍历dep执行每个副作用。activeEffect!.deps.push(dep),用于 cleanupEffect, finalizeDepMarkers。


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
  }

  let deps: (Dep | undefined)[] = []
  //清空操作
  if (type === TriggerOpTypes.CLEAR) {
    //取出target中所有key对应的dep
    deps = [...depsMap.values()]
  //target为数组且改变的是数组长度
  } else if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => {
      //取出key和大于长度新值下标的dep
      if (key === 'length' || key >= (newValue as number)) {
        deps.push(dep)
      }
    })
  } else {
    // key 不等于 undefined
    if (key !== void 0) {
      deps.push(depsMap.get(key))
    }

    // ADD | DELETE | SET
    switch (type) {
      case TriggerOpTypes.ADD:
         //不是数组则加入对应迭代key对应的dep
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
          //是数组则加入length对应的dep
        } else if (isIntegerKey(key)) {
          deps.push(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
       //不是数组则加入对应迭代key对应的dep
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        //加入迭代key对应的dep
        if (isMap(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  const eventInfo = __DEV__
    ? { target, type, key, newValue, oldValue, oldTarget }
    : undefined
   
   //如果只有一个,则直接调用triggerEffects
  if (deps.length === 1) {
    if (deps[0]) {
      if (__DEV__) {
        triggerEffects(deps[0], eventInfo)
      } else {
        triggerEffects(deps[0])
      }
    }
  } else {
    //effects:收集所有dep中的effect
    const effects: ReactiveEffect[] = []
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep)
      }
    }
    //使用createDep()处理effects返回一个dep,调用triggerEffects触发
    if (__DEV__) {
      triggerEffects(createDep(effects), eventInfo)
    } else {
      triggerEffects(createDep(effects))
    }
  }
}

首先判断target是否被收集过,如果没有则直接退出。
声明一个deps用来存储所有需要触发的dep。
1)
如果是TriggerOpTypes.CLEAR,则添加所有dep,如调Set或者Map的clear方法。
2)
如果是数组且是更改length,则添加length以及所有大于length下标的dep。
如 arr = [1,2,3], arr.length = 2,则添加length以及3对应的dep。
3)
key不等于undefined,添加其对应的dep。
判断触发操作类型 ADD | DELETE | SET
ADD:给普通对象或数组新增属性 或 Set.add, WeakSet.add
判断是不是数组,加入迭代key对应的dep或数组长度对应的dep。
DELETE: delete操作符 或 Map.delete, Set.delete, WeakMap.delete, WeakSet.delete
不是数组加入迭代key对应的dep。
SET: 修改普通对象或数组属性 或 Map.set, WeakMap.set
加入迭代key对应的dep。
4)
如果dep只有一个则直接触发。
否则合并成一个dep,在进行触发。


triggerEffects

export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // 遍历所有effect
  for (const effect of isArray(dep) ? dep : [...dep]) {
    //如果不是当前副作用 或者 允许递归
    if (effect !== activeEffect || effect.allowRecurse) {
      if (__DEV__ && effect.onTrigger) {
        effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
      }
      //有调度器则触发,否则run
      if (effect.scheduler) {
        effect.scheduler()
      } else {
        effect.run()
      }
    }
  }
}