中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Vue3 effectScope?API實現原理是什么

發布時間:2023-05-10 15:22:36 來源:億速云 閱讀:110 作者:iii 欄目:開發技術

本篇內容介紹了“Vue3 effectScope API實現原理是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

vue3新增effectScope相關的API

其官方的描述是創建一個 effect 作用域,可以捕獲其中所創建的響應式副作用 (即計算屬性和偵聽器),這樣捕獲到的副作用可以一起處理。并給出了示例:

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

// packages/reactivity/src/effectScope.ts
export function effectScope(detached?: boolean) {
  // 返回EffectScope實例
  return new EffectScope(detached)
}

EffectScope

export class EffectScope {
  /**
   * @internal
   */
  private _active = true
  /**
   * @internal
   */
  effects: ReactiveEffect[] = []
  /**
   * @internal
   */
  cleanups: (() => void)[] = []
  /**
   * only assigned by undetached scope
   * @internal
   */
  parent: EffectScope | undefined
  /**
   * record undetached scopes
   * @internal
   */
  scopes: EffectScope[] | undefined
  /**
   * track a child scope's index in its parent's scopes array for optimized
   * // index作用:在父作用域數組中跟蹤子作用域范圍索引以進行優化。
   * removal
   * @internal
   */
  private index: number | undefined
  constructor(public detached = false) {
    // 記錄當前scope為parent scope
    this.parent = activeEffectScope
    if (!detached && activeEffectScope) {
      this.index =
        (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(
          this
        ) - 1
    }
  }
  get active() {
    return this._active
  }
  run<T>(fn: () => T): T | undefined {
    if (this._active) {
      const currentEffectScope = activeEffectScope
      try {
        activeEffectScope = this
        return fn()
      } finally {
        activeEffectScope = currentEffectScope
      }
    } else if (__DEV__) {
      warn(`cannot run an inactive effect scope.`)
    }
  }
  /**
   * This should only be called on non-detached scopes
   * 必須在非分離的作用域上調用
   * @internal
   */
  on() {
    activeEffectScope = this
  }
  /**
   * This should only be called on non-detached scopes
   * @internal
   */
  off() {
    activeEffectScope = this.parent
  }
  // stop方法
  stop(fromParent?: boolean) {
    if (this._active) {
      let i, l
      // stop effects
      for (i = 0, l = this.effects.length; i < l; i++) {
        this.effects[i].stop()
      }
      // 執行所有的cleanups
      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.detached && 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.parent = undefined
      this._active = false
    }
  }
}

在執行scope.run的時候會將this賦值到全局的activeEffectScope變量,然后執行傳入函數。對于computed、watch、watchEffect(watchEffect是調用doWatch實現的,與watch實現響應式綁定的方式相同)這些API都會創建ReactiveEffect實例來建立響應式關系,而收集對應的響應式副作用就發生在ReactiveEffect創建的時候,我們來看一下ReactiveEffect的構造函數:

// ReactiveEffect的構造函數
constructor(
  public fn: () => T,
  public scheduler: EffectScheduler | null = null,
  scope?: EffectScope
) {
  // effect實例默認會被記錄到指定scope中
  // 如果沒有指定scope則會記錄到全局activeEffectScope中
  recordEffectScope(this, scope)
}
// recordEffectScope實現
export function recordEffectScope(
  effect: ReactiveEffect,
  // scope默認值為activeEffectScope
  scope: EffectScope | undefined = activeEffectScope
) {
  if (scope && scope.active) {
    scope.effects.push(effect)
  }
}

可以看到如果我們沒有傳入scope參數,那么在執行recordEffectScope時就會有一個默認的參數為activeEffectScope,這個值不正是我們scope.run的時候賦值的嗎!所以新創建的effect會被放到activeEffectScope.effects中,這就是響應式副作用的收集過程。
那么對于一起處理就比較簡單了,只需要處理scope.effects即可

組件的scope

日常開發中其實并不需要我們關心組件副作用的收集和清除,因為這些操作是已經內置好的,我們來看一下源碼中是怎么做的

組件實例中的scope

在組件實例創建的時候就已經new了一個屬于自已的scope對象了:

const instance: ComponentInternalInstance = {
  ...
  // 初始化scope
  scope: new EffectScope(true /* detached */),
  ...
}

在我們執行setup之前,會調用setCurrentInstance,他會調用instance.scope.on,那么就會將activeEffectScope賦值為instance.scope,那么在setup中注冊的computed、watch等就都會被收集到instance.scope.effects

function setupStatefulComponent(
  instance: ComponentInternalInstance,
  isSSR: boolean
) {
  // 組件對象
  const Component = instance.type as ComponentOptions
  ...
  // 2. call setup()
  const { setup } = Component
  if (setup) {
    // 創建setupContext
    const setupContext = (instance.setupContext =
      // setup參數個數判斷 大于一個參數創建setupContext
      setup.length > 1 ? createSetupContext(instance) : null)
    // instance賦值給currentInstance
    // 設置當前實例為instance 為了在setup中可以通過getCurrentInstance獲取到當前實例
    // 同時開啟instance.scope.on()
    setCurrentInstance(instance)
    // 暫停tracking 暫停收集副作用函數
    pauseTracking()
    // 執行setup
    const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      // setup參數
      [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
    )
    // 重新開啟副作用收集
    resetTracking()
    // currentInstance置為空 
    // activeEffectScope賦值為instance.scope.parent
    // 同時instance.scope.off()
    unsetCurrentInstance()
    ...
  } else {
    finishComponentSetup(instance, isSSR)
  }
}

對于選項式API的收集是同樣的操作:

// support for 2.x options
if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
  setCurrentInstance(instance)
  pauseTracking()
  // 處理options API
  applyOptions(instance)
  resetTracking()
  unsetCurrentInstance()
}

完成了收集那么對于清理就只需要在組件卸載的時候執行stop方法即可:

// packages/runtime-core/src/renderer.ts
const unmountComponent = (
  instance: ComponentInternalInstance,
  parentSuspense: SuspenseBoundary | null,
  doRemove?: boolean
) => {
  if (__DEV__ && instance.type.__hmrId) {
    unregisterHMR(instance)
  }
  const { bum, scope, update, subTree, um } = instance
  ...
  // stop effects in component scope
  // 副作用清除
  scope.stop()
  ...
}

“Vue3 effectScope API實現原理是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

弥勒县| 郧西县| 平度市| 楚雄市| 濮阳县| 格尔木市| 南皮县| 环江| 福清市| 阿克陶县| 吉水县| 二手房| 宜宾市| 漠河县| 馆陶县| 无棣县| 西安市| 枞阳县| 洞口县| 洛川县| 武强县| 朝阳区| 土默特右旗| 田林县| 光山县| 普洱| 庄河市| 华容县| 侯马市| 中西区| 株洲县| 沂南县| 扎赉特旗| 永和县| 贵定县| 柘荣县| 新乡市| 渭源县| 库伦旗| 崇阳县| 凯里市|