effect是一个函数

  1. const num = reactive({currentNum:0})
  2. let changeValue
  3. const callBackFn = ()=>{ changeValue = num.currentNum }
  4. effect(callBackFn)

effect 是一个随着依赖变化而执行自定义函数的函数?

  1. const num = reactive({currentNum:0})
  2. let changeValue
  3. const callBackFn = ()=>{ changeValue = num.currentNum }
  4. console.log(changeValue) // 0;
  5. effect(callBackFn)
  6. num.currentNum = 1;
  7. console.log(changeValue) // 1;

它们是怎样勾搭一起的?

effect 函数

  1. function effect<T = any>(
  2. fn: () => T,
  3. options: ReactiveEffectOptions = EMPTY_OBJ
  4. ): ReactiveEffect<T> {
  5. if (isEffect(fn)) {
  6. fn = fn.raw
  7. }
  8. const effect = createReactiveEffect(fn, options)
  9. if (!options.lazy) {
  10. effect()
  11. }
  12. return effect
  13. }

effect 函数做了三步工作:

  1. 是否已经被effect勾搭过
  2. 创建effect勾搭过程
  3. 是否默认执行effect函数

第一步分析省略…

createReactiveEffect函数

  1. function createReactiveEffect<T = any>(
  2. fn: () => T,
  3. options: ReactiveEffectOptions
  4. ): ReactiveEffect<T> {
  5. const effect = function reactiveEffect(...args: unknown[]): unknown {
  6. return run(effect, fn, args)
  7. } as ReactiveEffect
  8. effect._isEffect = true
  9. effect.active = true
  10. effect.raw = fn
  11. effect.deps = []
  12. effect.options = options
  13. return effect
  14. }

把目光移向 fn 变量,看看它被怎样处理:

  1. 被放进一个 reactiveEffect 函数
  2. 存放到一个叫 raw 的key

总的来说 createReactiveEffect 函数就是负责创建一个真正的 effect 函数的工作。但到现在还没有看到它们是怎么勾搭一起的,下面看看 run 函数。

run函数

  1. function run(effect: ReactiveEffect, fn: Function, args: unknown[]): unknown {
  2. if (!effect.active) {
  3. return fn(...args)
  4. }
  5. if (!effectStack.includes(effect)) {
  6. cleanup(effect)
  7. try {
  8. effectStack.push(effect)
  9. activeEffect = effect
  10. return fn(...args)
  11. } finally {
  12. effectStack.pop()
  13. activeEffect = effectStack[effectStack.length - 1]
  14. }
  15. }
  16. }

嗯!找到了!来!抓重点!看try-fially!

先分析try-finally的运行顺序:

  1. effectStack.push(effect)
  2. activeEffect = effect
  3. fn(…args)
  4. effectStack.pop()
  5. activeEffect = effectStack[effectStack.length - 1]

activeEffect ,这是一个全局变量。

官宣:2-3序列,就是勾搭过程了。

num.currentNum 与 fn 的关联在哪?

先来看看下面这段代码,是关于 Proxy 的 Get 阶段里的 track 函数:

  1. export function track(target: object, type: TrackOpTypes, key: unknown) {
  2. if (!shouldTrack || activeEffect === undefined) {
  3. return
  4. }
  5. let depsMap = targetMap.get(target)
  6. if (depsMap === void 0) {
  7. targetMap.set(target, (depsMap = new Map()))
  8. }
  9. let dep = depsMap.get(key)
  10. if (dep === void 0) {
  11. depsMap.set(key, (dep = new Set()))
  12. }
  13. if (!dep.has(activeEffect)) {
  14. dep.add(activeEffect)
  15. activeEffect.deps.push(dep)
  16. if (__DEV__ && activeEffect.options.onTrack) {
  17. activeEffect.options.onTrack({
  18. effect: activeEffect,
  19. target,
  20. type,
  21. key
  22. })
  23. }
  24. }
  25. }

还记得 fn 函数里的 changeValue = num.currentNum 吗?

num 去获取 currentNum 的时候,触发了 Get ,而当前没有存储此 effect ,所以进入到这里:

  1. if (!dep.has(activeEffect)) {
  2. dep.add(activeEffect)
  3. activeEffect.deps.push(dep)
  4. if (__DEV__ && activeEffect.options.onTrack) {
  5. activeEffect.options.onTrack({
  6. effect: activeEffect,
  7. target,
  8. type,
  9. key
  10. })
  11. }
  12. }

理所当然的被存储了,然后就是勾搭上了,因为每个key都会有对应的 dep 容器。

num.currentNum产生变化,回调存取函数

建立了关联,只需要在需要的时候,执行对应的 dep 容器就可以了。可以了解一下 trigger

:::info 若想了解详细的track、trigger的具体流程,请留意本系列相关文章,此处就忽略!链接 :::

effect第二个参数是什么?

从上面代码看到第二个options参数是一个对象,里面有:

  1. export interface ReactiveEffectOptions {
  2. lazy?: boolean
  3. computed?: boolean
  4. scheduler?: (run: Function) => void
  5. onTrack?: (event: DebuggerEvent) => void
  6. onTrigger?: (event: DebuggerEvent) => void
  7. onStop?: () => void
  8. }

未完待续!

:::tips 欢迎斧正! :::