阅读 83

vue-next: effect

记录一下 effect 的理解

首先看下effect.js中,都导出了什么? 除了接口和类型,文件由上至下分别导出

export {
  effect,         // 副作用函数
  stop,           // 停止
  pauseTracking,  // 暂停跟踪
  enableTracking, // 启用跟踪
  resetTracking,  // 重置跟踪
  track,          // 设置映射关系
  trigger,        // 派发更新
} from './effect'
复制代码

现在就来研究一下每个方法。

effect
// 涉及到的类型定义 or 方法
export function isEffect(fn: any): fn is ReactiveEffect {
  return fn && fn._isEffect === true
}
export interface ReactiveEffectOptions {
  lazy?: boolean
  scheduler?: (job: ReactiveEffect) => void
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
  onStop?: () => void
  allowRecurse?: boolean
}
// 正文
export function effect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
  // 是否已经处理过得函数
  // 不理解可以先略过,回头再看
  if (isEffect(fn)) {
    fn = fn.raw
  }
  // `变异函数`
  const effect = createReactiveEffect(fn, options)
  // lazy == falsy [假值]
  if (!options.lazy) {
    effect()
  }
  return effect
}


复制代码

可以看出来 effect 函数,主要是 return createReactiveEffect 方法的返回值,并判断是否需要立即执行。

createReactiveEffect
// 涉及到的类型定义 or 方法
const effectStack: ReactiveEffect[] = []; // effect 存储
export interface ReactiveEffect<T = any> {
  (): T
  _isEffect: true
  id: number
  active: boolean
  raw: () => T
  deps: Array<Dep>
  options: ReactiveEffectOptions
  allowRecurse: boolean
}

// 正文
function createReactiveEffect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions
): ReactiveEffect<T> {
  // 定义一个函数
  const effect = function reactiveEffect(): unknown {
    // 当这个函数执行时!
    
    if (!effect.active) {
      return options.scheduler ? undefined : fn()
    }
    // 当 effect 没有被 effectStack 收集时
    if (!effectStack.includes(effect)) {
      cleanup(effect)             // 清理 deps
      try {
        enableTracking()          // 启用追踪
        effectStack.push(effect)  // 收集
        activeEffect = effect     // 赋值
        return fn()               // 执行函数
      } finally {
        effectStack.pop()         // 删除
        resetTracking()           // 停止追踪
        activeEffect = effectStack[effectStack.length - 1]  // 赋值
      }
    }
  } as ReactiveEffect
  
  // 函数上增加一些属性, 注意下 `_isEffect` (再看回上文)
  effect.id = uid++
  effect.allowRecurse = !!options.allowRecurse
  effect._isEffect = true
  effect.active = true
  effect.raw = fn
  effect.deps = []
  effect.options = options
  
  return effect  // return 这个变异的函数
}

// 清理 deps
function cleanup(effect: ReactiveEffect) {
  const { deps } = effect
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0
  }
}
复制代码

这里可以看出来,createReactiveEffect 的函数,主要是返回一个经过处理后的变异 effect 函数, 并立即执行此函数。 那这个被赋值的 activeEffect 是什么呢?

activeEffect
export interface ReactiveEffect<T = any> {
  (): T
  _isEffect: true
  id: number
  active: boolean
  raw: () => T
  deps: Array<Dep>
  options: ReactiveEffectOptions
  allowRecurse: boolean
}
let activeEffect: ReactiveEffect | undefined
复制代码

可以看出就是一个变异函数,这里要记一下。

track

接下来看一下 track - 追踪函数。

let shouldTrack = true
// 下面的可以看成映射
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()

export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (!shouldTrack || activeEffect === undefined) {
    return
  }
  
  // 下面就是 设置映射关系 重要的就是这里
  // A. 获取,获取 target 的 映射 WeakMap -> Map
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  // B. 通过上面的 Map ,与入参的 key, 作第二次映射 Map -> Set
  //    dep 就是映射到最后的 value
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  
  // 这里判断 dep 是否已经设置映射的值
  if (!dep.has(activeEffect)) {
    // C. 设置映射的值 `activeEffect`
    dep.add(activeEffect)
    
    // 这里 deps 中添加 dep
    activeEffect.deps.push(dep)
    // 如果开发环境 并且 options.onTrack 存在,执行
    if (__DEV__ && activeEffect.options.onTrack) {
      activeEffect.options.onTrack({
        effect: activeEffect,
        target,
        type,
        key
      })
    }
  }
}
复制代码

这里主要是映射的关系 {target -> key -> dep} -> {WeakMap -> Map -> Set},也就是收集

trigger

接下来看一下 trigger 超级长的一串代码

// 类型定义
export const enum TriggerOpTypes {
  SET = 'set',
  ADD = 'add',
  DELETE = 'delete',
  CLEAR = 'clear'
}
export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '')
export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '')

export function trigger(
  target: object,            // 目标
  type: TriggerOpTypes,      // 类型
  key?: unknown,             // key
  newValue?: unknown,        // 新值
  oldValue?: unknown,        // 旧值
  oldTarget?: Map<unknown, unknown> | Set<unknown>   // 旧目标
) {
  // 获取 target 映射的 Map
  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)
        }
      })
    }
  }
  
  // 下面的可以理解成,在各种情况下, 根据 `track` 收集到的依赖映射
  // 也就是 `{target -> key -> dep} {WeakMap -> Map -> Set}` 中的 Set -> dep 
  // 根据 depsMap.get() 找到,并通过 add 方法 收集到 effects 中
  
  // A. 清除 - 遍历 Map
  if (type === TriggerOpTypes.CLEAR) {
    depsMap.forEach(add)
  // B. key = `length` && target 是一个数组
  } else if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= (newValue as number)) {
        add(dep)
      }
    })
  } else {
    // C. key !== undefined
    if (key !== void 0) {
      add(depsMap.get(key))
    }
    
    // D. switch -> 以下add 的入参都是  Set -> [activeEffect]
    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)) {
          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) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }
  // 遍历执行变异函数 `activeEffect`
  effects.forEach(run)
}
复制代码

可以看到 trigger 函数,主要就是通过映射关系,执行变异函数

shouldTrack

这里还有一段关于 shouldTrack 的函数,主要是起到开关作用。

let shouldTrack = true
const trackStack: boolean[] = []

export function pauseTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = false
}

export function enableTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = true
}

export function resetTracking() {
  const last = trackStack.pop()
  shouldTrack = last === undefined ? true : last
}
复制代码

就到这里,转载请声明出处。

文章分类
前端
文章标签