最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue3系列之effect和ReactiveEffect?track?trigger源碼解析

 更新時間:2022年10月27日 08:58:00   作者:ChrisLey  
這篇文章主要為大家介紹了Vue3系列之effect和ReactiveEffect?track?trigger源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

介紹幾個API的時候,我們發(fā)現(xiàn)里面常出現(xiàn)effect、tracktrigger,雖然簡單說了下track用于依賴收集,trigger來觸發(fā)更新。但是畢竟沒看到具體實現(xiàn),心里沒底。如今便可以一探究竟。

一、ReactiveEffect

1. 相關(guān)的全局變量

之前提到的effect,便是ReactiveEffect的實例。用到了一些重要的全局變量。

  • targetMap:弱映射,以目標對象targetkey,其收集到的依賴集depsMap為值,因此通過目標對象target可以獲取到對應(yīng)的所有依賴;
  • activeEffect:當前活躍的effect,隨后會被收集起來;
  • shouldTrack:用作暫停和恢復(fù)依賴收集的標志;
  • trackStack:歷史shouldTrack的記錄棧。

targetMap對比reactive篇章中提到的proxyMap

  • 兩者都是弱映射;
  • 都以目標對象targetkey
  • targetMap全局只有一個;而proxyMap有四種,分別對應(yīng)reactive、shallowReactive、readonlyshallowReadonly;
  • 一個target在一種proxyMap中最多只有一個對應(yīng)的代理proxy,因此proxyMap的值為單個的proxy對象;
  • 一個target可以由很多的依賴dep,因此targetMap的值為數(shù)據(jù)集Map。
const targetMap = new WeakMap<any, KeyToDepMap>()
export let activeEffect: ReactiveEffect | undefined
export let shouldTrack = true
const trackStack: boolean[] = []

以及控制暫停、恢復(fù)依賴收集的函數(shù):

// 暫停收集
export function pauseTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = false
}
// 恢復(fù)收集
export function enableTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = true
}
// 重置為上一次的狀態(tài)
export function resetTracking() {
  const last = trackStack.pop()
  shouldTrack = last === undefined ? true : last
}

2. class 聲明

在構(gòu)造器中初始化fn ( 執(zhí)行run()的過程中調(diào)用 ) 、調(diào)度器scheduler,并通過recordEffectScope來記錄實例的作用域;聲明一些實例屬性,以及run、stop兩個方法:

  • activeboolean類型,表示當前的effect是否起作用;
  • deps:當前effect的依賴;
  • parent:指向上一個活躍的effect,形成鏈表;
  • computed:可選,在computed函數(shù)得到的ComputedRefImpl里的effect具有這個屬性;
  • allowRecurse,可選,表示是否允許自調(diào)用;
  • deferStop:私有,可選,表示stop()是否延遲執(zhí)行;
  • onStop:可選,函數(shù),在執(zhí)行stop()時會調(diào)用onStop;
  • onTrack
  • onTrigger:這兩個listener為調(diào)試用,分別在依賴收集和響應(yīng)式更新時觸發(fā);
  • runeffect最核心的方法。
  • stop:調(diào)用cleanupEffecteffect停止起作用,如果是stop當前活躍的effect,也就是自己停止自己,則會將deferStop調(diào)為true,從而延遲停止的時機;觸發(fā)onStop;將active調(diào)為false
export class ReactiveEffect<T = any> {
  active = true
  deps: Dep[] = []
  parent: ReactiveEffect | undefined = undefined
  /**
   * Can be attached after creation
   * @internal
   */
  computed?: ComputedRefImpl<T>
  /**
   * @internal
   */
  allowRecurse?: boolean
  /**
   * @internal
   */
  private deferStop?: 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()
    }
    // 當前活躍的effect
    let parent: ReactiveEffect | undefined = activeEffect
    let lastShouldTrack = shouldTrack
    // 如果當前活躍的effect就是這個effect本身,則直接返回
    while (parent) {
      if (parent === this) {
        return
      }
      parent = parent.parent
    }
    // 依次活躍的effect形成鏈表,由parent屬性連接
    try {
      this.parent = activeEffect
      activeEffect = this
      shouldTrack = true
      trackOpBit = 1 << ++effectTrackDepth
      if (effectTrackDepth <= maxMarkerBits) {
        // 遍歷 this.deps 將其中的effect設(shè)置為已捕獲 tracked
        initDepMarkers(this)
      } else {
        // 層級溢出則清除當前副作用
        cleanupEffect(this)
      }
      // 尾調(diào)用傳入的fn
      return this.fn()
    } finally {
      // 因為前面有return,因此當 try 的代碼塊發(fā)生異常時執(zhí)行
      if (effectTrackDepth <= maxMarkerBits) {
        // 該方法遍歷 this.deps,將其中過氣的effect刪除,未捕獲的effect加入
        // effect 就是其中的 dep
        finalizeDepMarkers(this)
      }
      trackOpBit = 1 << --effectTrackDepth
      // 復(fù)原一些狀態(tài)
      activeEffect = this.parent
      shouldTrack = lastShouldTrack
      this.parent = undefined
      // 若設(shè)置了延遲停止,則執(zhí)行stop,進行延遲清理
      if (this.deferStop) {
        this.stop()
      }
    }
  }
  // 清除副作用
  stop() {
    // stopped while running itself - defer the cleanup
    if (activeEffect === this) {
      this.deferStop = true
    } else if (this.active) {
      cleanupEffect(this)
      if (this.onStop) {
        this.onStop()
      }
      this.active = false
    }
  }
}

3. cleanupEffect

cleanupEffect用于清除副作用。接收一個effect,遍歷effect.deps,并逐個刪除副作用effect。隨后清空effect.deps

function cleanupEffect(effect: ReactiveEffect) {
  const { deps } = effect
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0
  }
}

二、effect 函數(shù)

1. 相關(guān)ts類型

effect函數(shù)有幾個相關(guān)的類型:

  • ReactiveEffectOptionseffect函數(shù)的入?yún)㈩愋椭唬?/li>
  • ReactiveEffectRunner:是一個函數(shù),且具有effect屬性的類型;
export interface DebuggerOptions {
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}
export interface ReactiveEffectOptions extends DebuggerOptions {
  lazy?: boolean
  scheduler?: EffectScheduler
  scope?: EffectScope
  allowRecurse?: boolean
  onStop?: () => void
}
export interface ReactiveEffectRunner<T = any> {
  (): T
  effect: ReactiveEffect
}

2. 函數(shù)聲明

effect函數(shù)有兩個入?yún)ⅲ?/p>

  • fn:是一個函數(shù),經(jīng)處理后用于創(chuàng)建 ReactiveEffect實例_effect;
  • options:可選,用于覆蓋_effect上的屬性。
export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner {
  // 處理fn
  if ((fn as ReactiveEffectRunner).effect) {
    fn = (fn as ReactiveEffectRunner).effect.fn
  }
  // 根據(jù) fn 創(chuàng)建一個 _effect 
  const _effect = new ReactiveEffect(fn)
  if (options) {
    // 用 options 覆蓋 _effect 上的屬性
    extend(_effect, options)
    if (options.scope) recordEffectScope(_effect, options.scope)
  }
  // 沒有 lazy , 則 _effect 立即執(zhí)行一次 run()
  if (!options || !options.lazy) {
    _effect.run()
  }
  // runner:拿到 _effect.run 并掛上 effect 屬性,包裝成 ReactiveEffectRunner 類型
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  // effect屬性指回 _effect 自身,方便使用 runner 調(diào)用 run 和 stop
  runner.effect = _effect
  // 返回 runner
  return runner
}

3. stop函數(shù)

stop用于清除effect。入?yún)?code>ReactiveEffectRunner;

export function stop(runner: ReactiveEffectRunner) {
  runner.effect.stop()
}

三、track 依賴收集

1. track

一直在說track進行依賴收集,這里看下它到底怎么做的。

  • 以目標對象targetkey,depsMaptargetMap的值;以targetkeykey,使用createDep()創(chuàng)建依賴dep為值,存放在target對應(yīng)的depsMap中。
  • 通過trackEffects(dep, eventInfo)來收集副作用。
// 全局變量 targetMap
const targetMap = new WeakMap<any, KeyToDepMap>()
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)
  }
}

2. createDep

使用createDep創(chuàng)建一個新的dep??梢钥吹剑?code>dep是個Set實例,且添加了兩個屬性:

  • wwasTracked的首字母,表示當前依賴是否被收集;
  • nnewlyTracked的首字母,表示當前依賴是否是新收集的。
export const createDep = (effects?: ReactiveEffect[]): Dep => {
  const dep = new Set<ReactiveEffect>(effects) as Dep
  dep.w = 0
  dep.n = 0
  return dep
}

3. trackEffects

trackEffects用于收集副作用。主要把當前活躍的activeEffect加入dep,以及在activeEffect.deps中加入該副作用影響到的所有依賴。

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 {
    // Full cleanup mode.
    shouldTrack = !dep.has(activeEffect!)
  }
  // 當前依賴 dep 還未被捕獲 / 當前依賴 dep 中,還沒有當前活躍的副作用時,
  // 將當前活躍的副作用 effect 添加進 dep 里,同時在把 dep 加入受副作用影響的依賴集合 activeEffect.deps 中
  if (shouldTrack) {
    dep.add(activeEffect!)
    activeEffect!.deps.push(dep)
    if (__DEV__ && activeEffect!.onTrack) {
      activeEffect!.onTrack({
        effect: activeEffect!,
        ...debuggerEventExtraInfo!
      })
    }
  }
}

4. 小結(jié)

用一句比較拗口的話來說,依賴收集就是把當前活躍的副作用activeEffect存入全局變量targetMap中的 ( target 對應(yīng)的 depsMap) 中 (targetkey)對應(yīng)的 dep ( 類型為Set) 中,并把這個dep加入到受activeEffect副作用影響的所有依賴activeEffect.deps列表中。

四、trigger

觸發(fā)更新實際上就是觸發(fā)副作用,因此這一小節(jié)決定以與track相反的順序來介紹。

1. triggerEffect

triggerEffect觸發(fā)副作用從而更新。當觸發(fā)更新的副作用effect允許自調(diào)用,且不是當前活躍的副作用時,通過調(diào)度器scheduler執(zhí)行副作用或者直接執(zhí)行run,是實際上觸發(fā)更新的地方。

function triggerEffect(
  effect: ReactiveEffect,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  if (effect !== activeEffect || effect.allowRecurse) {
    if (__DEV__ && effect.onTrigger) {
      effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
    }
    // 實際觸發(fā)更新的地方
    if (effect.scheduler) {
      effect.scheduler()
    } else {
      effect.run()
    }
  }
}

2. triggerEffects

接收一個dep和用于調(diào)試的額外信息。遍歷dep中的effect,逐一使用triggerEffect來執(zhí)行副作用。源碼在這里有點蜜汁操作。

export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // spread into array for stabilization
  const effects = isArray(dep) ? dep : [...dep]
  // 兩者互斥,但是執(zhí)行的操作相同?而且為什么不寫在一個 for...of... 里 ?
  for (const effect of effects) {
    if (effect.computed) {
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
  for (const effect of effects) {
    if (!effect.computed) {
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
}

3. trigger

之前一直說trigger觸發(fā)更新,其實是現(xiàn)在已經(jīng)知道了,實際是triggerEffect來執(zhí)行副作用從而實現(xiàn)更新。

這里是創(chuàng)建一個deps數(shù)組,根據(jù)target、key和觸發(fā)更新的操作類型type等參數(shù),來獲取所有的相關(guān)dep,放入deps。再取出deps中所有的dep里的所有effect,放入effects列表中,通過triggerEffects(effects)來觸發(fā)所有的相關(guān)副作用,最終實現(xiàn)更新。

需要注意的是對于數(shù)組:

  • 修改length屬性會導(dǎo)致該數(shù)組所有依賴的更新;
  • 修數(shù)組新增成員會引起length屬性相關(guān)的依賴的更新,因為length的值發(fā)生了變化。
export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }
  // 用于聚集所有相關(guān)依賴
  let deps: (Dep | undefined)[] = []
  if (type === TriggerOpTypes.CLEAR) {
    // 調(diào)用了Set、Map實例的clear方法,將觸發(fā)全部相關(guān)的副作用
    // collection being cleared
    // trigger all effects for target
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {
    // 目標對象是數(shù)組,且修改了length屬性時,會觸發(fā)全部相關(guān)的副作用
    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))
    }
    // also run for iteration key on ADD | DELETE | Map.SET
    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)) {
          // 數(shù)組下標成員的更改 會引起 length 屬性相關(guān)的更新
          // 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)
      }
    }
    // 這里triggerEffects接受的參數(shù)類型為Set,之前的是數(shù)組
    if (__DEV__) {
      triggerEffects(createDep(effects), eventInfo)
    } else {
      triggerEffects(createDep(effects))
    }
  }
}

五、小結(jié)

1. 依賴收集

targetMap中有depsMap(以targetkey);depsMap中有許多dep(以targetMapkeykey);簡單理解為:在編譯時根據(jù)targetkey,創(chuàng)建副作用,將activeEffect指向新建的副作用,并存放到相關(guān)的依賴dep里的過程就是依賴收集。

2. 觸發(fā)更新

反過來,觸發(fā)targetkey相關(guān)的dep中所有相關(guān)的副作用,通過各個副作用上的effect.scheduler()或者effect.run()來實現(xiàn)更新。

以上就是Vue3系列之effect和ReactiveEffect track trigger源碼解析的詳細內(nèi)容,更多關(guān)于Vue3 effect和ReactiveEffect track trigger的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Vue手動控制點擊事件Click觸發(fā)方式

    Vue手動控制點擊事件Click觸發(fā)方式

    這篇文章主要介紹了Vue手動控制點擊事件Click觸發(fā)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • vue+php實現(xiàn)的微博留言功能示例

    vue+php實現(xiàn)的微博留言功能示例

    這篇文章主要介紹了vue+php實現(xiàn)的微博留言功能,結(jié)合完整實例形式分析了vue.js前臺留言及后臺php交互功能相關(guān)操作技巧,需要的朋友可以參考下
    2019-03-03
  • 關(guān)于vue?src路徑動態(tài)拼接的小知識

    關(guān)于vue?src路徑動態(tài)拼接的小知識

    這篇文章主要介紹了vue?src路徑動態(tài)拼接的小知識,具有很好的參考價值,希望對大家有所幫助。
    2022-04-04
  • Vue3實現(xiàn)pdf預(yù)覽功能

    Vue3實現(xiàn)pdf預(yù)覽功能

    在開發(fā)中,PDF預(yù)覽和交互功能是一個常見的需求,無論是管理系統(tǒng)、在線教育平臺,還是企業(yè)內(nèi)部的知識庫,能夠高效地展示和操作PDF文件都能極大地提升用戶體驗,本文將詳細介紹如何在Vue項目中實現(xiàn)pdf預(yù)覽功能,需要的朋友可以參考下
    2025-03-03
  • Vue如何基于es6導(dǎo)入外部js文件

    Vue如何基于es6導(dǎo)入外部js文件

    這篇文章主要介紹了Vue如何基于es6導(dǎo)入外部js文件,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-05-05
  • vue addRoutes路由動態(tài)加載操作

    vue addRoutes路由動態(tài)加載操作

    這篇文章主要介紹了vue addRoutes路由動態(tài)加載操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • 從組件封裝看Vue的作用域插槽的實現(xiàn)

    從組件封裝看Vue的作用域插槽的實現(xiàn)

    這篇文章主要介紹了從組件封裝看Vue的作用域插槽的實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-02-02
  • 詳解Vue路由傳參的兩種方式query和params

    詳解Vue路由傳參的兩種方式query和params

    這篇文章主要介紹了Vue路由傳參的兩種方式query和params,介紹了query和params區(qū)別與總結(jié),結(jié)合示例代碼給大家介紹的非常詳細,需要的朋友可以參考下
    2022-09-09
  • 在Vue中導(dǎo)入并讀取Excel數(shù)據(jù)的操作步驟

    在Vue中導(dǎo)入并讀取Excel數(shù)據(jù)的操作步驟

    在工作中遇到需要前端上傳excel文件獲取到相應(yīng)數(shù)據(jù)處理之后傳給后端并且展示上傳文件的數(shù)據(jù),所以本文就來給大家介紹一下Vue中導(dǎo)入并讀取Excel數(shù)據(jù)的操作步驟,需要的朋友可以參考下
    2023-08-08
  • Vue-Jest 自動化測試基礎(chǔ)配置詳解

    Vue-Jest 自動化測試基礎(chǔ)配置詳解

    目前開發(fā)大型應(yīng)用,測試是一個非常重要的環(huán)節(jié),而在 Vue 項目中做單元測試可以用 Jest,本文主要介紹了Vue-Jest 自動化測試,感興趣的可以了解一下
    2021-07-07

最新評論

成武县| 浏阳市| 孝昌县| 怀仁县| 公安县| 砀山县| 沂水县| 蒙山县| 萨嘎县| 房产| 博客| 临潭县| 曲水县| 卫辉市| 盐城市| 南昌县| 章丘市| 谷城县| 平潭县| 长春市| 汉中市| 姚安县| 福鼎市| 铁岭县| 云阳县| 即墨市| 柞水县| 康平县| 洞口县| 扶余县| 三穗县| 乐亭县| 蒙阴县| 上思县| 青海省| 垣曲县| 桐庐县| 吴忠市| 视频| 乌兰察布市| 华阴市|