乐趣区

关于前端:vue3源码三effectScope源码解析

前言

参考代码版本: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}
退出移动版