美文网首页【vue3源码】
【vue3源码】二、vue3的响应系统分析

【vue3源码】二、vue3的响应系统分析

作者: MAXLZ | 来源:发表于2022-06-29 14:58 被阅读0次

    vue3的响应系统分析

    前言

    参考代码版本:vue 3.2.37

    官方文档:https://vuejs.org/

    vue3的响应式处理主要集中在packages/reactivity/src/effect.ts文件中。

    effect

    vue3中,会使用一个effect方法注册副作用函数。为什么要注册副作用函数呢?

    如果响应式数据更新,我们希望副作用函数中的相关数据也能同步更新。要实现这种效果,就需要我们做两个工作:

    • 读取响应式数据时,收集副作用函数。
    • 设置响应式数据时,触发副作用函数。

    那么我们如何在设置响应式数据时,触发相关的副作用函数呢?这就需要我们在收集副作用函数时,使用某种数据结构把他暂存起来,等到需要到他的时候,就可以取出来。

    effect的作用就是将我们注册的副作用函数暂存。下面我们来看effect的实现:

    export function effect<T = any>(
      fn: () => T,
      options?: ReactiveEffectOptions
    ): ReactiveEffectRunner {
      if ((fn as ReactiveEffectRunner).effect) {
        fn = (fn as ReactiveEffectRunner).effect.fn
      }
    
      const _effect = new ReactiveEffect(fn)
      if (options) {
        extend(_effect, options)
        if (options.scope) recordEffectScope(_effect, options.scope)
      }
      if (!options || !options.lazy) {
        _effect.run()
      }
      const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
      runner.effect = _effect
      return runner
    }
    

    effect可以接收两个参数,其中第二个参数为可选参数,可以不传。第一个参数是一个副作用函数fn,第二个参数是个对象,该对象可以有如下属性:

    • lazyboolean,是否懒加载,如果是true,调用effect不会立即执行监听函数,需要用户手动执行
    • scheduler:一个调度函数,如果存在调度函数,在触发依赖时,执行该调度函数
    • scope:一个EffectScope作用域对象
    • allowRecurseboolean,允许递归
    • onStopeffect被停止时的钩子

    effect中会首先检查fn.effect属性,如果存在fn.effect,那么说明fn已经被effect处理过了,然后使用fn.effect.fn作为fn

    if ((fn as ReactiveEffectRunner).effect) {
      fn = (fn as ReactiveEffectRunner).effect.fn
    }
    
    const fn = () => {}
    const runner1 = effect(fn)
    const runner2 = effect(runner1)
    
    runner1.effect.fn === fn // true
    runner2.effect.fn === fn // true
    

    然后new了一个ReactiveEffect对象。

    const _effect = new ReactiveEffect(fn)
    

    接着如果存在option对象的话,会将options,合并到_effect中。如果存在options.scope,会调用recordEffectScope_effect放入options.scope。如果不存在optionsoptions.lazy === false,那么会执行_effect.run(),进行依赖的收集。

    if (options) {
      extend(_effect, options)
      if (options.scope) recordEffectScope(_effect, options.scope)
    }
    if (!options || !options.lazy) {
      _effect.run()
    }
    

    最后,会将_effect.run中的this指向它本身,这样做的目的是用户在主动执行runner时,this指针指向的是_effect对象,然后将_effect作为runnereffect属性,并将runner返回。

    const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
    runner.effect = _effect
    return runner
    

    effect中创建了一个ReactiveEffect对象,这个ReactiveEffect是什么呢?接下来继续看ReactiveEffect的实现。

    ReactiveEffect

    export class ReactiveEffect<T = any> {
      active = true
      deps: Dep[] = []
      parent: ReactiveEffect | undefined = undefined
    
      computed?: ComputedRefImpl<T>
      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
      ) {
        recordEffectScope(this, scope)
      }
    
      run() {
        if (!this.active) {
          return this.fn()
        }
        let parent: ReactiveEffect | undefined = activeEffect
        let lastShouldTrack = shouldTrack
        while (parent) {
          if (parent === this) {
            return
          }
          parent = parent.parent
        }
        try {
          this.parent = activeEffect
          activeEffect = this
          shouldTrack = true
    
          trackOpBit = 1 << ++effectTrackDepth
    
          if (effectTrackDepth <= maxMarkerBits) {
            initDepMarkers(this)
          } else {
            cleanupEffect(this)
          }
          return this.fn()
        } finally {
          if (effectTrackDepth <= maxMarkerBits) {
            finalizeDepMarkers(this)
          }
    
          trackOpBit = 1 << --effectTrackDepth
    
          activeEffect = this.parent
          shouldTrack = lastShouldTrack
          this.parent = undefined
        }
      }
    
      stop() {
        if (this.active) {
          cleanupEffect(this)
          if (this.onStop) {
            this.onStop()
          }
          this.active = false
        }
      }
    }
    

    ReactiveEffect是使用es6 class定义的一个类。它的构造器可以接受三个参数:fn(副作用函数)、scheduler(调度器)、scope(一个EffectScope作用域对象),在构造器中调用了一个recordEffectScope方法,这个方法会将当前ReactiveEffect对象(this)放入对应的EffectScope作用域(scope)中。

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

    ReactiveEffect中有两个方法:runstop

    run

    run的执行过程中,会首先判断ReactiveEffect的激活状态(active),如果未激活(this.active === false),那么会立马执行this.fn并返回他的执行结果。

    if (!this.active) {
      return this.fn()
    }
    

    然后声明了两个变量:parent(默认activeEffect)、lastShouldTrack(默认shouldTrack,一个全局变量,默认为true)。紧接着会使用while循环寻找parent.parent,一旦parentthis相等,立即结束循环。

    let parent: ReactiveEffect | undefined = activeEffect
    let lastShouldTrack = shouldTrack
    while (parent) {
      if (parent === this) {
        return
      }
      parent = parent.parent
    }
    

    紧接着把activeEffect赋值给this.parent,把this赋值给this.parent

    try {
      // 设置当前的parent为上一个activeEffect
      this.parent = activeEffect
      // 设置activeEffect为当前ReactiveEffect实例,activeEffect是个全局变量
      activeEffect = this
      shouldTrack = true
      // ...
    }
    // ...
    

    这样做的目的是,建立一个嵌套effect的关系,来看下面一个例子:

    const obj = reactive({a: 1})
    
    effect(() => {
      console.log(obj.a)
      effect(() => {
        console.log(obj.a)
      })
    })
    

    当执行第一层_effect.run时,因为默认的activeEffectundefined,所以第一层effect中的_effect.parent=undefined,紧接着把this赋值给activeEffect,这时activeEffect指向的第一层的_effect

    在第一层中的_effect.run执行过程中,最后会执行this.fn(),在执行this.fn()的过程中,会创建第二层effectReactiveEffect对象,然后执行_effect.run,因为在第一层中_effect.run运行过程中,已经将第一层的_effect赋给了activeEffect,所以第二层中的_effect.parent指向了第一层的_effect,紧接着又将第二次的_effect赋给了activeEffect。这样以来第一层effect与第二层effect就建立了联系。

    当与父effect建立联系后,有这么一行代码:

    trackOpBit = 1 << ++effectTrackDepth
    

    其中effectTrackDepth是个全局变量为effect的深度,层数从1开始计数,trackOpBit使用二进制标记依赖收集的状态(如00000000000000000000000000000010表示所处深度为1)。

    紧接着会进行一个条件的判断:如果effectTrackDepth未超出最大标记位(maxMarkerBits = 30),会调用initDepMarkers方法将this.deps中的所有dep标记为已经被track的状态;否则使用cleanupEffect移除deps中的所有dep

    if (effectTrackDepth <= maxMarkerBits) {
      initDepMarkers(this)
    } else {
      cleanupEffect(this)
    }
    

    这里为什么要标记已经被track的状态或直接移除所有dep?我们来看下面一个例子:

    const obj = reactive({ str: 'objStr', flag: true })
    
    effect(() => {
      const c = obj.flag ? obj.str : 'no found'
      console.log(c)
    })
    
    obj.flag = false
    
    obj.str = 'test'
    

    在首次track时,targetMap结构如下(targetMap在下文中有介绍):

    effect-flow1.png

    这时targetMap[toRaw(obj)](这里targetMap的键是obj的原始对象)中分别保存着strflag共两份依赖。当执行obj.flag=false后,会触发flag对应的依赖,此时打印not found

    obj.flag变为false之后,副作用函数就不会受obj.str的影响了,之后的操作,无论obj.str如何变化,都不应该影响到副作用函数。这里标记dep为已被track或移除dep的作用就是实现这种效果。由于obj.flag的修改,会触发flag对应的副作用函数(执行run函数),此时this.deps中保存着strflag的对应的两份依赖,所以调用initDepMarkers后,会将这两份依赖标记为已收集,当this.fn()执行完毕后,会根据dep某些属性,将str所对应的依赖移除。这样无论修改str为和值,都没有对应的依赖触发。

    所以initDepMarkers(在finally移除)/cleanupEffect的作用是移除多余的依赖。

    回到run函数中,最后需要执行this.fn(),并将结果返回。这样就可以进行依赖的收集。在return fn()之后继续进入finally,在finally中需要恢复一些状态:finalizeDepMarkers根据一些状态移除多余的依赖、将effectTrackDepth回退一层,activeEffect指向当前ReactiveEffectparentshouldTrack = lastShouldTrackthis.parent置为undefined

    try {
      // ...
      
      return this.fn()
    } finally {
      if (effectTrackDepth <= maxMarkerBits) {
        finalizeDepMarkers(this)
      }
    
      trackOpBit = 1 << --effectTrackDepth
    
      activeEffect = this.parent
      shouldTrack = lastShouldTrack
      this.parent = undefined
    }
    

    run函数的作用就是会调用fn,并返回其结果,在执行fn的过程中会命中响应式对象的某些拦截操作,在拦截过程中进行依赖的收集。

    stop

    当调用stop函数后,会调用cleanupEffectReactiveEffect中所有的依赖删除,然后执行onStop钩子,最后将this.active置为false

    stop() {
    if (this.active) {
        cleanupEffect(this)
        if (this.onStop) {
          this.onStop()
        }
        this.active = false
      }
    }
    

    依赖收集

    通过上面对effect的分析,在effect中如果未设置options.lazy = false的话,会直接执行_effect.run(),而在run()方法中最后最终会调用副作用函数fn。在fn的执行过程中,会读取某个响应式数据,而我们的响应式数据是被Proxy代理过的,一旦读取响应式数据的某个属性,就会触发Proxyget操作(不一定是get,这里以get为例进行说明)。在拦截过程中会触发一个track函数。

    export function track(target: object, type: TrackOpTypes, key: unknown) {
      if (shouldTrack && activeEffect) {
        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)
      }
    }
    

    track函数接收三个参数:target(响应式对象的原始对象)、type(触发依赖操作的方式,有三种取值:TrackOpTypes.GETTrackOpTypes.HASTrackOpTypes.ITERATE)、key(触发依赖收集的key)。

    track中一上来就对shouldTrackactiveEffect进行了判断,只有shouldTracktrue且存在activeEffect时才可以进行依赖收集。

    如果可以进行依赖收集的话,会从targetMap中获取target对应的值,这里targetMap保存着所有响应式数据所对应的副作用函数,它是个WeakMap类型的全局变量,WeakMap的键是响应式数据的原始对象target,值是个Map,而Map的键是原始对象的keyMap的值时一个由副作用函数(一个ReactiveEffect实例)组成的Set集合。

    为什么target要使用WeakMap,而不是Map?因为WeakMap的键是弱引用,如果target被销毁后,那么它对应的值Map也会被回收。如果你不了解WeakMap的使用,请参考:MDN

    type KeyToDepMap = Map<any, Dep>
    const targetMap = new WeakMap<any, KeyToDepMap>()
    
    targetMap.png

    如果从targetMap找不到target对应的值,则创建一个Map对象,存入targetMap中。

    let depsMap = targetMap.get(target)
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()))
    }
    

    然后从depsMap中获取key对应的副作用集合,如果不存在,则创建一个Set,存入depsMap中。这里创建Set的过程中,会为Set实例添加两个属性:nww表示在副作用函数执行前dep是否已经被收集过了,n表示在当前收集(本次run执行)过程中dep是新收集的。

    let dep = depsMap.get(key)
    if (!dep) {
      depsMap.set(key, (dep = createDep()))
    }
    

    最后调用trackEffects方法。

    const eventInfo = __DEV__
      ? { effect: activeEffect, target, type, key }
      : undefined
    
    trackEffects(dep, eventInfo)
    
    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 {
        // 直接判断dep中是否含有activeEffect
        shouldTrack = !dep.has(activeEffect!)
      }
    
      if (shouldTrack) {
        dep.add(activeEffect!)
        activeEffect!.deps.push(dep)
        if (__DEV__ && activeEffect!.onTrack) {
          activeEffect!.onTrack(
            Object.assign(
              {
                effect: activeEffect!
              },
              debuggerEventExtraInfo
            )
          )
        }
      }
    }
    

    trackEffects接收两个参数:depReactiveEffect集合),debuggerEventExtraInfo(开发环境下activeEffect.onTrack钩子所需的参数)。

    trackEffects中说先声明了一个默认值为falseshouldTrack变量,它代表我们需不需要收集activeEffect

    如果shouldTracktrue的话,则将activeEffect添加到dep中,同时将dep放入activeEffect.deps中。

    shouldTrack的确定和depnw属性密切相关。如果newTracked(dep) === true,说明在本次run方法执行过程中,dep已经被收集过了,shouldTrack不变;如果newTracked(dep) === false,要把dep标记为新收集的,虽然dep在本次收集过程中是新收集的,但它可能在之前的收集过程中已经被收集了,所以shouldTrack的值取决于dep是否在之前已经被收集过了。

    // wasTracked(dep)返回true,意味着dep在之前的依赖收集过程中已经被收集过,或者说在之前run执行过程中已经被收集
    export const wasTracked = (dep: Dep): boolean => (dep.w & trackOpBit) > 0
    
    // newTracked(dep)返回true,意味着dep是在本次依赖收集过程中新收集到的,或者说在本次run执行过程中新收集到的
    export const newTracked = (dep: Dep): boolean => (dep.n & trackOpBit) > 0
    

    这里使用以下例子来说明shouldTrack的确认过程:

    let sum
    const counter = reactive({ num1: 0, num2: 0 })
    effect(() => {
      sum = counter.num1 + counter.num1 + counter.num2
    })
    

    在上面例子中共经历3次依赖收集的过程。

    1. 第一次因为访问到counter.num1,被counterget拦截器拦截,因为最开始targetMap是空的,所以在第一次收集过程中会进行初始化,此时targetMap[toRaw(counter)].num1.n/w=0,当决定shouldTrack的值时,因为newTracked(dep)===false,所以shouldTrack=!wasTracked,显然wasTracked(dep)===falseshouldTrack值被确定为true,意味着依赖应该被收集,track执行完成后的targetMap结构为

      track-flow1.png
    2. 第二次同样访问到counter.num1,被counterget拦截器拦截,并开始收集依赖,但在这次收集过程中,因为newTracked(dep) === true,所以shouldTrackfalse,本次不会进行依赖的收集

    3. 第三次访问到counter.num2,过程与第一次相同,当本次track执行完毕后,targetMap结构为

      track-flow2.png
    4. 3次依赖收集完毕,意味着fn执行完毕,进入finally中,执行finalizeDepMarkers,此时会将_effect.deps中的dep.n恢复至0

    触发依赖

    在依赖被收集完成后,一旦响应式数据的某些属性改变后,就会触发对应的依赖。这个触发的过程发生在proxysetdeleteProperty拦截器、,或集合的get拦截器(拦截clearaddset等操作)。

    在触发依赖时,会执行一个trigger函数:

    export function trigger(
      target: object,
      type: TriggerOpTypes,
      key?: unknown,
      newValue?: unknown,
      oldValue?: unknown,
      oldTarget?: Map<unknown, unknown> | Set<unknown>
    ) {
       // 获取target对相应的所有依赖,一个map对象 
       const depsMap = targetMap.get(target)
       // 如果没有,说明没有依赖,直接return
       if (!depsMap) {
          return
       }
    
       // 获取需要触发的依赖
       let deps: (Dep | undefined)[] = []
       if (type === TriggerOpTypes.CLEAR) {
          // collection being cleared
          // trigger all effects for target
          deps = [...depsMap.values()]
       } else if (key === 'length' && isArray(target)) {
          depsMap.forEach((dep, key) => {
             if (key === 'length' || key >= (newValue as number)) {
                deps.push(dep)
             }
          })
       } else {
          // schedule runs for SET | ADD | DELETE
          if (key !== void 0) {
             deps.push(depsMap.get(key))
          }
    
          // 获取一些迭代的依赖,如map.keys、map.values、map.entries等
          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
    
       // 开始触发依赖
       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))
          }
       }
    }
    

    trigger可接收六个参数:

    • target:响应式数据的原始对象
    • type:操作类型。是个枚举类TriggerOpTypes,共有四种操作类型:
      • TriggerOpTypes.SET:如obj.xx = xx(修改属性)、map.set(xx, xx)(修改操作不是新增操作)、arr[index] = xx(index < arr.length)、arr.length = 0
      • TriggerOpTypes.ADD:如obj.xx = xx(新增属性)、set.add(xx)map.set(xx, xx)(新增操作)、arr[index] = xx(index >= arr.length)
      • TriggerOpTypes.DELETE:如delete obj.xxset/map.delete(xx)
      • TriggerOpTypes.CLEAR:如map/set.clear()
    • key:可选,触发trigger的键,如obj.foo = 1keyfoo
    • newValue:可选,新的值,如obj.foo = 1newValue1
    • oldValue:可选,旧的值,如obj.foo = 1oldValue为修改前的obj.foo
    • oldTarget:可选,旧的原始对象,只在开发模式下有用。

    trigger中首先要获取target对应的所有依赖depsMap,如果没有的直接return

    const depsMap = targetMap.get(target)
    if (!depsMap) {
     return
    }
    

    接下来需要根据keytype获取触发的依赖(使用deps存放需要触发的依赖),这里分为如下几个分支:

    • type === TriggerOpTypes.CLEAR:意味着调用了map/set.clear()map/set被清空,这时与map/set相关的所有依赖都需要被触发。
    deps = [...depsMap.values()]
    
    • key === 'length' && isArray(target):当操作的的是arraylength属性,如arr.length = 1,这时要获取的依赖包括:length属性的依赖以及索引大于等于新的length的依赖。
    depsMap.forEach((dep, key) => {
       if (key === 'length' || key >= (newValue as number)) {
         deps.push(dep)
       }
    })
    
    • 其他情况:
      • 首先从depsMap中获取对应key的依赖,depsMap.get(key)
      if (key !== void 0) { // void 0 等价于undefined
       deps.push(depsMap.get(key))
      }
      
      • 然后再找一些迭代的依赖,如keys、values、entries操作。
      • TriggerOpTypes.ADD:如果不是数组,获取ITERATE_KEY的依赖,如果是Map获取MAP_KEY_ITERATE_KEY的依赖;如果是数组并且key是索引,获取length对应的依赖
      if (!isArray(target)) { // target不是数组
         deps.push(depsMap.get(ITERATE_KEY))
         if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
         }
      } else if (isIntegerKey(key)) { // key是整数,获取length对应的依赖
         deps.push(depsMap.get('length'))
      }
      
      • TriggerOpTypes.DELETE:如果不是数组,获取ITERATE_KEY的依赖,如果是Map获取MAP_KEY_ITERATE_KEY的依赖
      if (!isArray(target)) {
       deps.push(depsMap.get(ITERATE_KEY))
       if (isMap(target)) {
         deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
       }
      }
      
      • TriggerOpTypes.SET:如果是Map,获取ITERATE_KEY的依赖
      if (isMap(target)) {
       deps.push(depsMap.get(ITERATE_KEY))
      }
      

    这里简单介绍下ITERATE_KEYMAP_KEY_ITERATE_KEY存储的依赖是由什么操作引起的

    ITERATE_KEY中的依赖是由这些操作触发进行收集:获取集合的size、集合的forEach操作、集合的迭代操作(包括keys(非Map)、valuesentriesSymbol.iteratorfor...of))

    MAP_KEY_ITERATE_KEY中的依赖的由map.keys()触发进行收集。

    对应上面其他情况中的几个分支:

    • 如果对响应式数据的改动是一种新增操作的话,受影响的操作有:集合的size、集合的forEach、集合的迭代操作。
    • 如果改动是删除操作,受影响的操作有:集合的size、集合的forEach、集合的迭代操作。
    • 如果改动是修改操作,因为只有map.set()可以实现修改集合的操作,所以受影响的操作只有Map的迭代操作和forEach

    当收集完需要触发的依赖,下一步就是要触发依赖:

    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))
     }
    }
    

    这里有两个分支:

    • 如果deps.length为1,且存在des[0],则调用triggerEffects(deps[0])
    • 否则将遍历deps并解构,将每一个effect放入一个effects中,然后在调用triggerEffects时,利用Set去重:triggerEffects(createDep(effects))

    triggerEffects函数可以接收两个参数:dep一个数组或Set集合,保存着需要触发的依赖、debuggerEventExtraInfo在开发环境下,effect.onTrigger所需的一些信息。

    export function triggerEffects(
      dep: Dep | ReactiveEffect[],
      debuggerEventExtraInfo?: DebuggerEventExtraInfo
    ) {
      for (const effect of isArray(dep) ? dep : [...dep]) {
        if (effect !== activeEffect || effect.allowRecurse) {
          if (__DEV__ && effect.onTrigger) {
            effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
          }
          if (effect.scheduler) {
            effect.scheduler()
          } else {
            effect.run()
          }
        }
      }
    }
    

    triggerEffects中,会遍历dep,如果dep中的effect不是当前活跃的effectactiveEffect)或effect.allowRecursetrue,则会根据是否有effect.scheduler,执行effect.schedulereffect.run。 至此,依赖触发过程结束。

    接下来详细看下在this.fn()执行完毕后,多余的依赖是如何根据nw属性移除的(此处值只考虑深度在31层以内的,超出31(包含31)层会直接调用cleanupEffect方法删除,比较简单,此处不进行详细说明):
    我们还是以前面的例子来分析:

    const obj = reactive({ str: 'objStr', flag: true })
    
    effect(() => {
      const c = obj.flag ? obj.str : 'no found'
      console.log(c)
    })
    
    obj.flag = false
    
    obj.str = 'test'
    
    1. effect执行过程中,创建ReactiveEffect实例,这里以_effect表示,因为未指定lazy,所以会执行_effect.run()
    2. 执行this.fn(),在fn执行过程中会访问到objflagstr属性,从而被objget拦截器进行拦截,在拦截过程中会调用track进行依赖的收集,this.fn()执行完毕后targetMap结构如下
      effect-flow1.png
    3. 然后进入finally,执行finalizeDepMarkers,因为wasTracked(dep)false,所以不会删除依赖,但会执行dep.n &= ~trackOpBit,清除比特位。最终targetMap结构为:
      effect-flow2.png
    4. 当执行obj.flag = false时,会触发flag属性对应的依赖,执行trigger,在trigger中获取flag对应的依赖set1,然后调用triggerEffects,在triggerEffects中,执行_effect.run
    5. 在这次run执行过程中,会将_effect.deps中的依赖集合都标记为已收集状态:
      effect-flow3.png
    6. 然后执行this.fn(),同样执行fn的过程中,被objget拦截器拦截,不过这次只拦截了flag属性。在trackEffects中检测到newTracked(dep) === false(此处dep就是set1),所以执行dep.n |= trackOpBit操作,将set1标记为本轮收集过程中新的依赖,又因为wasTracked(dep) === true,所以shouldTrackfalse,本次不会收集依赖。至此,targetMap结构为:
      effect-flow4.png
    7. this.fn()执行完毕,进入finally,执行finalizeDepMarkers。在finalizeDepMarkers中会遍历effect.deps,根据nw属性移除依赖。
    8. 首先判断set1,因为wasTracked(dep) === truenewTracked(dep) === true,所以执行deps[ptr++] = dep,将set1放在deps索引为0的位置,同时ptr自增1,然后执行dep.w &= ~trackOpBitdep.n &= ~trackOpBit。最终set1.n/w = 0
    9. 接着判断set2,因为wasTracked(dep) === truenewTracked(dep) === false,所以执行dep.delete(effect),将_effectset2中删除,然后执行dep.w &= ~trackOpBitdep.n &= ~trackOpBit。最终set2.n/w = 0set2中无依赖。
    10. 遍历完毕,执行deps.length = ptrptr此时为1)。也就是说把set2deps中移除了。
    11. finally执行完毕后,targetMap结构为:
      effect-flow5.png
      可以看到str对应的依赖已经没有了。
    12. 当执行obj.str = 'test'时,触发trigger函数,但此时在targetMap中已经没有str对应的依赖了,所以在trigger中直接return,结束。

    相关文章

      网友评论

        本文标题:【vue3源码】二、vue3的响应系统分析

        本文链接:https://www.haomeiwen.com/subject/nxntbrtx.html