美文网首页【vue3源码】
【vue3源码】三、effectScope源码解析

【vue3源码】三、effectScope源码解析

作者: MAXLZ | 来源:发表于2022-06-30 21:00 被阅读0次

    前言

    参考代码版本:vue 3.2.37

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

    关于为什么要有effectScope可以参考RFC

    使用示例

    effectScope可以对内部的响应式对象的副作用effect进行统一管理。

    const counter = ref(1)
    const scope = effectScope()
    scope.run(() => {
      const doubled = computed(() => counter.value * 2)
    
      watch(doubled, () => console.log(doubled.value))
    
      watchEffect(() => console.log('Count: ', doubled.value))
    })
    
    // 处理掉当前作用域内的所有 effect
    scope.stop()
    

    effectScope接收一个boolean值,如果传true代表游离模式,那么创建的scope不会被父scope收集,通俗来讲,如果是游离模式,那么scope之间是不存在父子关系的,每一个scope都是独立的。

    export function effectScope(detached?: boolean) {
      return new EffectScope(detached)
    }
    

    effectScope返回一个EffectScope实例。

    EffectScope

    export class EffectScope {
      active = true
      effects: ReactiveEffect[] = []
      cleanups: (() => void)[] = []
    
      parent: EffectScope | undefined
      scopes: EffectScope[] | undefined
      /**
       * track a child scope's index in its parent's scopes array for optimized
       * removal
       */
      private index: number | undefined
    
      constructor(detached = false) {
        if (!detached && activeEffectScope) {
          this.parent = activeEffectScope
          this.index =
            (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(
              this
            ) - 1
        }
      }
    
      run<T>(fn: () => T): T | undefined {
        if (this.active) {
          try {
            activeEffectScope = this
            return fn()
          } finally {
            activeEffectScope = this.parent
          }
        } else if (__DEV__) {
          warn(`cannot run an inactive effect scope.`)
        }
      }
    
      on() {
        activeEffectScope = this
      }
    
      off() {
        activeEffectScope = this.parent
      }
    
      stop(fromParent?: boolean) {
        if (this.active) {
          let i, l
          for (i = 0, l = this.effects.length; i < l; i++) {
            this.effects[i].stop()
          }
          for (i = 0, l = this.cleanups.length; i < l; i++) {
            this.cleanups[i]()
          }
          if (this.scopes) {
            for (i = 0, l = this.scopes.length; i < l; i++) {
              this.scopes[i].stop(true)
            }
          }
          // nested scope, dereference from parent to avoid memory leaks
          if (this.parent && !fromParent) {
            // optimized O(1) removal
            const last = this.parent.scopes!.pop()
            if (last && last !== this) {
              this.parent.scopes![this.index!] = last
              last.index = this.index!
            }
          }
          this.active = false
        }
      }
    }
    

    constructor

    EffectScope构造器接收一个参数:detached,默认值为false,代表EffectScope是否是游离状态。

    constructor(detached = false) {
      if (!detached && activeEffectScope) {
        this.parent = activeEffectScope
        this.index =
          (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(
            this
          ) - 1
      }
    }
    

    如果detachedfalse,并且存在activeEffectScopeactiveEffectScope是个全局变量)的情况,会将activeEffectScope赋值给this.parent,同时会将当前EffectScope实例放入activeEffectScope.scopes中,并将activeEffectScope.scopes最后一个索引赋值给当前EffectScope实例的index属性。这样就可以通过this.index来获取EffectScope实例在父scope中的索引位置。

    run

    run方法可以接收一个函数参数。

    run<T>(fn: () => T): T | undefined {
      if (this.active) {
        try {
          activeEffectScope = this
          return fn()
        } finally {
          activeEffectScope = this.parent
        }
      } else if (__DEV__) {
        warn(`cannot run an inactive effect scope.`)
      }
    }
    

    run方法会首先对this.active进行判断,如果this.activetrue,也就是EffectScope处于激活状态,那么会将this赋给activeEffectScope,然后执行fn,并返回其执行结果。当fn执行完毕后,将activeEffectScope改为this.parent

    on

    
    on() {
      activeEffectScope = this
    }
    

    on方法会将activeEffectScope指向当前EffectScope实例。

    off

    off() {
      activeEffectScope = this.parent
    }
    

    off方法会将activeEffectScope指向当前EffectScope实例的父scope

    stop

    stop函数的作用是清除scope内的所有的响应式效果,包括子scopestop接收一个boolean类型的fromParent参数,如果fromParenttruestop将不会删除在父scope中的引用。

    stop(fromParent?: boolean) {
      if (this.active) {
        let i, l
        // 调用ReactiveEffect.prototype.stop,清除scope内所有响应式效果
        for (i = 0, l = this.effects.length; i < l; i++) {
          this.effects[i].stop()
        }
        // 触发scope销毁时的监听函数
        for (i = 0, l = this.cleanups.length; i < l; i++) {
          this.cleanups[i]()
        }
        // 销毁子scope
        if (this.scopes) {
          for (i = 0, l = this.scopes.length; i < l; i++) {
            this.scopes[i].stop(true)
          }
        }
        // 嵌套范围,从父级取消引用以避免内存泄漏
        if (this.parent && !fromParent) {
          // 获取父scope的中最后一个scope
          const last = this.parent.scopes!.pop()
          // last不是当前的scope
          if (last && last !== this) {
            // 将last放在当前scope在parent.scopes中的索引位置
            this.parent.scopes![this.index!] = last
            // last.index改为this.index
            last.index = this.index!
          }
        }
        // 修改scope的激活状态
        this.active = false
      }
    }
    

    stop中的所有操作都要建立在scope处于激活状态的基础上。首先遍历this.effects执行元素的stop方法。

    for (i = 0, l = this.effects.length; i < l; i++) {
      this.effects[i].stop()
    }
    

    scope.effects存储的是在run过程中获取到的ReactiveEffect实例,这些ReactiveEffect实例会通过一个recordEffectScope方法被添加到scope.effects中。

    export function recordEffectScope(
      effect: ReactiveEffect,
      scope: EffectScope | undefined = activeEffectScope
    ) {
      if (scope && scope.active) {
        scope.effects.push(effect)
      }
    }
    

    当遍历完scope.effects或,会遍历scope.cleanups属性。

    for (i = 0, l = this.cleanups.length; i < l; i++) {
          this.cleanups[i]()
        }
    

    scope.cleanups中保存的是通过onScopeDispose添加的scope销毁监听函数。

    export function onScopeDispose(fn: () => void) {
      if (activeEffectScope) {
        activeEffectScope.cleanups.push(fn)
      } else if (__DEV__) {
        warn(
          `onScopeDispose() is called when there is no active effect scope` +
            ` to be associated with.`
        )
      }
    }
    

    如果当前scope存在scopes属性,意味着当前scope存在子scope,所以需要将所有子scope也进行销毁。

    if (this.scopes) {
      for (i = 0, l = this.scopes.length; i < l; i++) {
        this.scopes[i].stop(true)
      }
    }
    

    如果当前scope存在parent的话,需要将scope从其parent中移除。

    if (this.parent && !fromParent) {
      // 获取父scope的中最后一个scope
      const last = this.parent.scopes!.pop()
      // last不是当前的scope
      if (last && last !== this) {
        // 将last放在当前scope在parent.scopes中的索引位置
        this.parent.scopes![this.index!] = last
        // last.index改为this.index
        last.index = this.index!
      }
    }
    

    这里的移除过逻辑是,先获取当前scope的父scope中的所有子scope,然后取出最后一个scope,这里用last代表(注意last不一定和当前scope相同),如果last和当前scope不同的话,需要让last替换当前scope,这样我们就把当前scope从其父scope中移除了。这里仅仅替换是不够的,因为last.index此时还是之前父scope的最后一个索引,所以还需要把last.index改为当前scope在其父scope.scopes中的位置。这样就完全移除了scope

    最后,需要把scope的激活状态改为false

    this.active = false
    

    getCurrentScope

    getCurrentScope可以获取当前处于活跃状态的EffectScope。这里处于活跃状态的EffectScope指得是当前执行环境在所处的那个EffectScope

    export function getCurrentScope() {
      return activeEffectScope
    }
    

    相关文章

      网友评论

        本文标题:【vue3源码】三、effectScope源码解析

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