Vue3源码阅读(八)effect

effect

  • effect 作为 reactive 的核心,主要负责收集依赖,更新依赖,其会在 mountComponent、doWatch、reactive、computed 时被调用。
  • 实质:其实就是一个改良版的发布订阅模式。get 时通过 track 收集依赖,而 set 时通过 trigger 触发了依赖,而 effect 收集了这些依赖并进行追踪,在响应后去触发相应的依赖。effect 也正是 Vue3 响应式的核心。
  • 参数
    • fn 回调函数
    • options 参数
  • 执行
    • 在调用 effect 时会触发 track 开启响应式追踪,将追踪数据放入 targetMap
    • 执行 reactive 时,通过 Proxy 类劫持对象
      • 劫持 getter 执行 track
      • 劫持 setter 执行 trigger
    • 劫持的对象放在一个叫 targetMap 的 WeakMap
export interface ReactiveEffectOptions {
  lazy?: boolean //  是否延迟触发 effect
  computed?: boolean // 是否为计算属性
  scheduler?: (job: ReactiveEffect) => void // 调度函数
  onTrack?: (event: DebuggerEvent) => void // 追踪时触发
  onTrigger?: (event: DebuggerEvent) => void // 触发回调时触发
  onStop?: () => void // 停止监听时触发
}


export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner {
  // 如果已经是effect,先重置为原始对象
  if ((fn as ReactiveEffectRunner).effect) {
    fn = (fn as ReactiveEffectRunner).effect.fn
  }
  // 创建`effect`
  const _effect = new ReactiveEffect(fn)
  if (options) {
    extend(_effect, options)
    if (options.scope) recordEffectScope(_effect, options.scope)
  }
  // 如果没有传入 lazy 则不延迟触发,直接执行一次 `effect`
  if (!options || !options.lazy) {
    _effect.run()
  }
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect
  return runner
}

  • effect的创建
    • 首先对 effect 做了一些初始化,然后初次创建 effect 的时候,如果当前的 effect 栈(effectStack)不包含当前 effect,仅将activeEffect设为当前effect,将activeEffect压入effectStack再开始依赖收集,根据依赖数目判断是否需要清空依赖数组,这样可以避免依赖的重复收集。依赖收集后重置activeEffect。这里的effect实际上是vue中垃圾回收
export class ReactiveEffect<T = any> {
  active = true
  deps: Dep[] = []

  // can be attached after creation
  computed?: boolean
  allowRecurse?: boolean
  onStop?: () => void
  // dev only
  onTrack?: (event: DebuggerEvent) => void
  // dev only
  onTrigger?: (event: DebuggerEvent) => void

  constructor(
    public fn: () => T,
    public scheduler: EffectScheduler | null = null,
    scope?: EffectScope | null
  ) {
    recordEffectScope(this, scope)
  }

  run() {
    // 如果没有调度者,直接执行fn
    if (!this.active) {
      return this.fn()
    }
    // 判断effectStack中有没有effect, 如果在则不处理
    if (!effectStack.includes(this)) {
      try {
        // 如果不在则将activeEffect设为当前effect,将activeEffect压入effectStack
        effectStack.push((activeEffect = this))
        // 开始重新依赖收集
        enableTracking()
        // 依赖数目+1
        trackOpBit = 1 << ++effectTrackDepth
        if (effectTrackDepth <= maxMarkerBits) {
          // 依赖数目小于最大调用数,清空依赖数组
          initDepMarkers(this)
        } else {
          // 清除effect依赖,定义在下方
          cleanupEffect(this)
        }
        // 返回回调
        return this.fn()
      } finally {
        if (effectTrackDepth <= maxMarkerBits) {
         // 依赖数目小于最大调用数,清空依赖数组后继续重新进行依赖收集
          finalizeDepMarkers(this)
        }
        // 依赖数目-1
        trackOpBit = 1 << --effectTrackDepth
        // 重置依赖
        resetTracking()
        // 完成后将effect弹出
        effectStack.pop()
        const n = effectStack.length
        // 重置activeEffect 
        activeEffect = n > 0 ? effectStack[n - 1] : undefined
      }
    }
  }
 // 仅在依赖数组中清除传入的effect依赖
  function cleanupEffect(effect: ReactiveEffect) {
  const { deps } = effect
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0
  }
}
// dep.ts
// 每次 effect 运行都会重新收集依赖, deps 是 effect 的依赖数组, 需要全部清空
export const initDepMarkers = ({ deps }: ReactiveEffect) => {
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].w |= trackOpBit // set was tracked
    }
  }
}
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
  }
}
  • track 收集依赖(get操作)
    • 当对一个对象进行get、has、iterate的时候,会触发该对象的track,收集依赖到targetMap。
// track

// get、 has、 iterate 三种类型的读取对象会触发 track
export const enum TrackOpTypes {
  GET = 'get',
  HAS = 'has',
  ITERATE = 'iterate'
}
let shouldTrack = true

// target:目标对象;type:收集的类型;key: 触发 track 的 object 的 key 
export function track(target: object, type: TrackOpTypes, key: unknown) {
  // activeEffect为空没有依赖或者不应当触发track时,直接return
  if (!isTracking()) {
    return
  }
  // targetMap是依赖管理中心,用于收集依赖和触发依赖 
  // 检查targetMap中有没有当前target  
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    // 如果目标对象不存在于targetMap,即没有被追踪,则新建一个放入targetMap
    targetMap.set(target, (depsMap = new Map()))
  }
  // 检车depsMap中是否存在触发track的key
  let dep = depsMap.get(key)
  if (!dep) {
    // 如果目标 key 没有被追踪,添加一个
    depsMap.set(key, (dep = createDep()))
  }
  const eventInfo = __DEV__
    ? { effect: activeEffect, target, type, key }
    : undefined
  trackEffects(dep, eventInfo)
}
// activeEffect不为空且应当Track
export function isTracking() {
  return shouldTrack && activeEffect !== undefined
}
// deps来收集依赖函数,当监听的 key 值发生变化时,触发dep中的依赖函数
export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  let shouldTrack = false
  if (effectTrackDepth <= maxMarkerBits) {
    if (!newTracked(dep)) {
      dep.n |= trackOpBit // set newly tracked
      shouldTrack = !wasTracked(dep)
    }
  } else {
    // Full cleanup mode.
    shouldTrack = !dep.has(activeEffect!)
  }

  if (shouldTrack) {
    dep.add(activeEffect!)
    activeEffect!.deps.push(dep)
    // 开发环境会触发onTrack, 仅用于调试
    if (__DEV__ && activeEffect!.onTrack) {
      activeEffect!.onTrack(
        Object.assign(
          {
            effect: activeEffect!
          },
          debuggerEventExtraInfo
        )
      )
    }
  }
}
  • trigger 触发依赖(触发更新后执行监听函数之前触发)
    • 对对象进行set、add、delete、clear时会触发trigger,使用target中的deps触发依赖追踪。
    • trigger的运行过程。其实是消费targetMap的依赖。在 trigger 方法中,拿到了之前收集到的依赖(也就是之前添加好的 effect)并添加到了任务队列中。然后遍历找到依赖后,开始触发依赖,执行任务
// trigger
// 会触发依赖的几种操作类型
export const enum TriggerOpTypes {
  SET = 'set',
  ADD = 'add',
  DELETE = 'delete',
  CLEAR = 'clear'
}

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) {
    // never been tracked
    return
  }

  // 对依赖进行分类  
  let deps: (Dep | undefined)[] = []
  if (type === TriggerOpTypes.CLEAR) {
    // 正在进行清除操作
    // 触发目标的所有效果
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {
    //如果是数组的length修改且不是清除操作, 这里就能监听到数组的 length 变化了
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= (newValue as number)) {
        // 如果是数组的length修改的新增,则push进deps
        deps.push(dep)
      }
    })
  } else {
    // 如果是新增/删除/编辑操作且目标 key 没有被追踪,添加一个
    if (key !== void 0) {
      deps.push(depsMap.get(key))
    }

    // 在 新增/删除/编辑 的方法中,判断了 target 的类型然后添加 depsMap 中的不同依赖到 effect 中
    // effects 代表普通依赖,
    // computedRunners 为计算属性依赖 
    // 都是 Set 结构,避免重复收集
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          deps.push(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        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 {
    const effects: ReactiveEffect[] = []
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep)
      }
    }
    if (__DEV__) {
      triggerEffects(createDep(effects), eventInfo)
    } else {
      triggerEffects(createDep(effects))
    }
  }
}

export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // spread into array for stabilization
  for (const effect of isArray(dep) ? dep : [...dep]) {
    if (effect !== activeEffect || effect.allowRecurse) {
      if (__DEV__ && effect.onTrigger) {
        effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
      }
      // 如果 scheduler 存在则调用 scheduler,计算属性拥有 scheduler
      if (effect.scheduler) {
        effect.scheduler()
      } else {
        effect.run()
      }
    }
  }
}

  • 总结
    • 调用方调用effect函数,参数为函数fn,options(默认为{});
    • 判断是否已经是effect过的函数,如果是的话,则直接把原函数返回。
    • 调用createReactiveEffect生成当前fn对应的effect函数,把上面的参数fn和options直接传进去;
      • 为effect函数赋值
      • 然后为effect函数添加属性:id, _isEffect, active, raw, deps, options,把effect返回。
    • 判断options里面的lazy是否是false
      • 如果不是懒处理,就直接调用下对应的effect函数,返回生成的effect函数。
      • 如果是懒处理
        • 首先判断了是否是active状态,如果不是,说明当前effect函数已经处于失效状态,直接返回return options.scheduler ? undefined : fn()
        • 查看调用栈effectStack里面是否有当前effect,如果无当前effect,接着执行下面的代码。
        • 先调用cleanup,把当前所有依赖此effect的全部清掉,deps是个数组,元素为Set,Set里面放的则是ReactiveEffect,也就是effect;
        • 把当前effect入栈,并将当前effect置为当前活跃effect->activeEffect;后执行fn函数;
        • finally,把effect出栈,执行完成了,把activeEffect还原到之前的状态;
        • 其中涉及到调用轨迹栈的记录。和shouldTrack是否需要跟踪轨迹的处理。

effect.spec单元测试

  • 部分注意点,非全部源码
// 传递给effect的方法,会立即执行一次
it('should run the passed function once (wrapped by a effect)', () => {
  const fnSpy = jest.fn(() => {})
  effect(fnSpy)
  expect(fnSpy).toHaveBeenCalledTimes(1)
})
// 在 effect 执行将 observe 对基本类型赋值,observe 进行改变时,将反应到基本类型上
it('should observe basic properties', () => {
  let dummy
  const counter = reactive({ num: 0 })
  effect(() => (dummy = counter.num))

  expect(dummy).toBe(0)
  counter.num = 7
  expect(dummy).toBe(7)
})
// 在多个 effect 中处理 observe,当 observe 发生改变时,将同步到多个 effect
it('should handle multiple effects', () => {
  let dummy1, dummy2
  const counter = reactive({ num: 0 })
  effect(() => (dummy1 = counter.num))
  effect(() => (dummy2 = counter.num))
  expect(dummy1).toBe(0)
  expect(dummy2).toBe(0)
  counter.num++
  expect(dummy1).toBe(1)
  expect(dummy2).toBe(1)
})
// 嵌套的 observe 做出改变时,也会产生响应
it('should observe nested properties', () => {
  let dummy
  const counter = reactive({ nested: { num: 0 } })
  effect(() => (dummy = counter.nested.num))
  expect(dummy).toBe(0)
  counter.nested.num = 8
  expect(dummy).toBe(8)
}) 
// 在 effect 执行将 observe 对基本类型赋值,observe 进行删除操作时,将反应到基本类型上
it('should observe delete operations', () => {
  let dummy
  const obj = reactive({ prop: 'value' })
  effect(() => (dummy = obj.prop))
  expect(dummy).toBe('value')
  delete obj.prop
  expect(dummy).toBe(undefined)
})
// 在 effect 执行将 observe in 操作,observe 进行删除操作时,将反应到基本类型上
it('should observe has operations', () => {
  let dummy
  const obj = reactive<{ prop: string | number }>({ prop: 'value' })
  effect(() => (dummy = 'prop' in obj))
  expect(dummy).toBe(true)
  delete obj.prop
  expect(dummy).toBe(false)
  obj.prop = 12
  expect(dummy).toBe(true)
})
// this 会被响应
it('should observe chained getters relying on this', () => {
  const obj = reactive({
    a: 1,
    get b() {
      return this.a
    }
  })

  let dummy
  effect(() => (dummy = obj.b))
  expect(dummy).toBe(1)
  obj.a++
  expect(dummy).toBe(2)
})

it('should observe methods relying on this', () => {
  const obj = reactive({
    a: 1,
    b() {
      return this.a
    }
  })

  let dummy
  effect(() => (dummy = obj.b()))
  expect(dummy).toBe(1)
  obj.a++
  expect(dummy).toBe(2)
})
// 改变原始对象不产生响应
it('should not observe raw mutations', () => {
  let dummy
  const obj = reactive<{ prop?: string }>({})
  effect(() => (dummy = toRaw(obj).prop))

  expect(dummy).toBe(undefined)
  obj.prop = 'value'
  expect(dummy).toBe(undefined)
})

it('should not be triggered by raw mutations', () => {
  let dummy
  const obj = reactive<{ prop?: string }>({})
  effect(() => (dummy = obj.prop))

  expect(dummy).toBe(undefined)
  toRaw(obj).prop = 'value'
  expect(dummy).toBe(undefined)
})

it('should not be triggered by inherited raw setters', () => {
  let dummy, parentDummy, hiddenValue: any
  const obj = reactive<{ prop?: number }>({})
  const parent = reactive({
    set prop(value) {
      hiddenValue = value
    },
    get prop() {
      return hiddenValue
    }
  })
  Object.setPrototypeOf(obj, parent)
  effect(() => (dummy = obj.prop))
  effect(() => (parentDummy = parent.prop))

  expect(dummy).toBe(undefined)
  expect(parentDummy).toBe(undefined)
  toRaw(obj).prop = 4
  expect(dummy).toBe(undefined)
  expect(parentDummy).toBe(undefined)
})
// 可以避免隐性递归导致的无限循环
it('should avoid implicit infinite recursive loops with itself', () => {
  const counter = reactive({ num: 0 })

  const counterSpy = jest.fn(() => counter.num++)
  effect(counterSpy)
  expect(counter.num).toBe(1)
  expect(counterSpy).toHaveBeenCalledTimes(1)
  counter.num = 4
  expect(counter.num).toBe(5)
  expect(counterSpy).toHaveBeenCalledTimes(2)
})

it('should avoid infinite loops with other effects', () => {
  const nums = reactive({ num1: 0, num2: 1 })
  const spy1 = jest.fn(() => (nums.num1 = nums.num2))
  const spy2 = jest.fn(() => (nums.num2 = nums.num1))
  effect(spy1)
  effect(spy2)
  expect(nums.num1).toBe(1)
  expect(nums.num2).toBe(1)
  expect(spy1).toHaveBeenCalledTimes(1)
  expect(spy2).toHaveBeenCalledTimes(1)
  nums.num2 = 4
  expect(nums.num1).toBe(4)
  expect(nums.num2).toBe(4)
  expect(spy1).toHaveBeenCalledTimes(2)
  expect(spy2).toHaveBeenCalledTimes(2)
  nums.num1 = 10
  expect(nums.num1).toBe(10)
  expect(nums.num2).toBe(10)
  expect(spy1).toHaveBeenCalledTimes(3)
  expect(spy2).toHaveBeenCalledTimes(3)
})
// 结果未发生变动时不做处理,发生改变时应该产生响应
it('should discover new branches while running automatically', () => {
  let dummy
  const obj = reactive({ prop: 'value', run: false })

  const conditionalSpy = jest.fn(() => {
    dummy = obj.run ? obj.prop : 'other'
  })
  effect(conditionalSpy)

  expect(dummy).toBe('other')
  expect(conditionalSpy).toHaveBeenCalledTimes(1)
  obj.prop = 'Hi'
  expect(dummy).toBe('other')
  expect(conditionalSpy).toHaveBeenCalledTimes(1)
  obj.run = true
  expect(dummy).toBe('Hi')
  expect(conditionalSpy).toHaveBeenCalledTimes(2)
  obj.prop = 'World'
  expect(dummy).toBe('World')
  expect(conditionalSpy).toHaveBeenCalledTimes(3)
})

it('should discover new branches when running manually', () => {
  let dummy
  let run = false
  const obj = reactive({ prop: 'value' })
  const runner = effect(() => {
    dummy = run ? obj.prop : 'other'
  })

  expect(dummy).toBe('other')
  runner()
  expect(dummy).toBe('other')
  run = true
  runner()
  expect(dummy).toBe('value')
  obj.prop = 'World'
  expect(dummy).toBe('World')
})

it('should not be triggered by mutating a property, which is used in an inactive branch', () => {
  let dummy
  const obj = reactive({ prop: 'value', run: true })

  const conditionalSpy = jest.fn(() => {
    dummy = obj.run ? obj.prop : 'other'
  })
  effect(conditionalSpy)

  expect(dummy).toBe('value')
  expect(conditionalSpy).toHaveBeenCalledTimes(1)
  obj.run = false
  expect(dummy).toBe('other')
  expect(conditionalSpy).toHaveBeenCalledTimes(2)
  obj.prop = 'value2'
  expect(dummy).toBe('other')
  expect(conditionalSpy).toHaveBeenCalledTimes(2)
})
// 传入参数 scheduler, 支持自定义调度
it('scheduler', () => {
  let runner: any, dummy
  const scheduler = jest.fn(_runner => {
    runner = _runner
  })
  const obj = reactive({ foo: 1 })
  effect(
    () => {
      dummy = obj.foo
    },
    { scheduler }
  )
  expect(scheduler).not.toHaveBeenCalled()
  expect(dummy).toBe(1)
  // should be called on first trigger
  obj.foo++
  expect(scheduler).toHaveBeenCalledTimes(1)
  // should not run yet
  expect(dummy).toBe(1)
  // manually run
  runner()
  // should have run
  expect(dummy).toBe(2)
})
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值