溫馨提示×

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

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Vue3副作用函數(shù)與依賴(lài)收集怎么實(shí)現(xiàn)

發(fā)布時(shí)間:2023-05-12 09:15:38 來(lái)源:億速云 閱讀:119 作者:zzz 欄目:編程語(yǔ)言

本篇內(nèi)容主要講解“Vue3副作用函數(shù)與依賴(lài)收集怎么實(shí)現(xiàn)”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Vue3副作用函數(shù)與依賴(lài)收集怎么實(shí)現(xiàn)”吧!

副作用函數(shù)

副作用函數(shù)是指會(huì)產(chǎn)生副作用的函數(shù),如下面的代碼所示:

function effect(){
  document.body.innerText = 'hello vue3'
}

當(dāng) effect 函數(shù)執(zhí)行時(shí),它會(huì)設(shè)置 body 的文本內(nèi)容,但除了 effect 函數(shù)之外的任何函數(shù)都可以讀取或設(shè)置 body 的文本內(nèi)容。也就是說(shuō),effect 函數(shù)的執(zhí)行會(huì)直接或間接影響其他函數(shù)的執(zhí)行,這時(shí)我們說(shuō) effect 函數(shù)產(chǎn)生了副作用。副作用很容易產(chǎn)生,例如一個(gè)函數(shù)修改了全局變量,這其實(shí)也是一個(gè)副作用。

// 全局變量
let val = 1
function effect() {
  val = 2 // 修改全局變量,產(chǎn)生副作用
}

副作用函數(shù)的全局變量

在副作用模塊中,定義了幾個(gè)全局的變量,提前認(rèn)識(shí)這些變量有助與我們了解副作用函數(shù)的生成以及調(diào)用的過(guò)程。

// packages/reactivity/src/effect.ts
export type Dep = Set<ReactiveEffect> & TrackedMarkers
type KeyToDepMap = Map<any, Dep>
// WeakMap 集合存儲(chǔ)副作用函數(shù)
const targetMap = new WeakMap<any, KeyToDepMap>()

// 用一個(gè)全局變量存儲(chǔ)當(dāng)前激活的 effect 函數(shù)
export let activeEffect: ReactiveEffect | undefined

// 標(biāo)識(shí)是否開(kāi)啟了依賴(lài)收集
export let shouldTrack = true
const trackStack: boolean[] = []
targetMap

targetMap 是一個(gè) WeakMap 類(lèi)型的集合,用來(lái)存儲(chǔ)副作用函數(shù),從類(lèi)型定義可以看出 targetMap的數(shù)據(jù)結(jié)構(gòu)方式:

  • WeakMap 由 target --> Map 構(gòu)成

  • Map 由 key --> Set 構(gòu)成

其中 WeakMap 的鍵是原始對(duì)象 target,WeakMap 的值是一個(gè) Map 實(shí)例,Map 的鍵是原始對(duì)象 target 的 key,Map 的值是一個(gè)由副作用函數(shù)組成的 Set。它們的關(guān)系如下:

Vue3副作用函數(shù)與依賴(lài)收集怎么實(shí)現(xiàn)

targetMap 為什么使用 WeakMap

我們來(lái)看下面的代碼:

const map = new Map();
const weakMap = new WeakMap();

(function() {
  const foo = {foo: 1};
  const bar = {bar: 2};
  
  map.set(foo, 1); // foo 對(duì)象是 map 的key
  weakMap.set(bar, 2); // bar 對(duì)象是 weakMap 的 key
})

在上面的代碼中,定義了 map 和 weakMap 常量,分別對(duì)應(yīng) Map 和 WeakMap 的實(shí)例。在立即執(zhí)行的函數(shù)表達(dá)式內(nèi)部定義了兩個(gè)對(duì)象:foo 和 bar,這兩個(gè)對(duì)象分別作為 map 和 weakMap 的key。

當(dāng)函數(shù)表達(dá)式執(zhí)行完畢后,對(duì)于對(duì)象 foo 來(lái)說(shuō),它仍然作為 map 的 key 被引用著,因此垃圾回收器不會(huì)把它從內(nèi)存中移除,我們?nèi)匀豢梢酝ㄟ^(guò) map.keys 打印出對(duì)象 foo 。

對(duì)于對(duì)象 bar 來(lái)說(shuō),由于 WeakMap 的 key 是弱引用,它不影響垃圾收集器的工作,所以一旦表達(dá)式執(zhí)行完畢,垃圾回收器就會(huì)把對(duì)象 bar 從內(nèi)存中移除,并且我們無(wú)法獲取 weakMap 的 key 值,也就無(wú)法通過(guò) weakMap 取得對(duì)象 bar 。

簡(jiǎn)單地說(shuō),WeakMap 對(duì) key 是弱引用,不影響垃圾回收器的工作**。根據(jù)這個(gè)特性可知,一旦 key 被垃圾回收器回收,那么對(duì)應(yīng)的鍵和值就訪(fǎng)問(wèn)不到了。所以 WeakMap 經(jīng)常用于存儲(chǔ)那些只有當(dāng) key 所引用的對(duì)象存在時(shí) (沒(méi)有被回收) 才有價(jià)值的信息**。

例如在上面的場(chǎng)景中,如果 target 對(duì)象沒(méi)有任何引用了,說(shuō)明用戶(hù)側(cè)不再需要它了,這時(shí)垃圾回收器會(huì)完成回收任務(wù)。但如果使用 Map 來(lái)代替 WeakMap,那么即使用戶(hù)側(cè)的代碼對(duì) target 沒(méi)有任何引用,這個(gè) target 也不會(huì)被回收,最終可能導(dǎo)致內(nèi)存溢出。

activeEffect

activeEffect 變量用來(lái)維護(hù)當(dāng)前正在執(zhí)行的副作用

shouldTrack

shouldTrack 變量用來(lái)標(biāo)識(shí)是否開(kāi)啟依賴(lài)搜集,只有 shouldTrack 的值為 true 時(shí),才進(jìn)行依賴(lài)收集,即將副作用函數(shù)添加到依賴(lài)集合中。

副作用的實(shí)現(xiàn)

effect 函數(shù)

effect API 用來(lái)創(chuàng)建一個(gè)副作用函數(shù),接受兩個(gè)參數(shù),分別是用戶(hù)自定義的fn函數(shù)和options 選項(xiàng)。源碼如下所示:

// packages/reactivity/src/effect.ts

export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner {
  // 當(dāng)傳入的 fn 中存在 effect 副作用時(shí),將這個(gè)副作用的原始函數(shù)賦值給 fn
  if ((fn as ReactiveEffectRunner).effect) {
    fn = (fn as ReactiveEffectRunner).effect.fn
  }

  // 創(chuàng)建一個(gè)副作用 
  const _effect = new ReactiveEffect(fn)

  if (options) {
    extend(_effect, options)
    if (options.scope) recordEffectScope(_effect, options.scope)
  }

//   如果不是延遲執(zhí)行的,則立即執(zhí)行一次副作用函數(shù)
  if (!options || !options.lazy) {
    _effect.run()
  }
  // 通過(guò) bind 函數(shù)返回一個(gè)新的副作用函數(shù)   
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  // 將副作用添加到新的副作用函數(shù)上   
  runner.effect = _effect
  // 返回這個(gè)新的副作用函數(shù)   
  return runner
}

由上面的代碼可以知道,當(dāng)傳入的參數(shù) fn 中存在 effect 副作用時(shí),將這個(gè)副作用的原始函數(shù)賦值給 fn。然后調(diào)用 ReactiveEffect 類(lèi)創(chuàng)建一個(gè)封裝后的副作用函數(shù)。

在有些場(chǎng)景下,我們不希望 effect 立即執(zhí)行,而是希望它在需要的時(shí)候才執(zhí)行,我們可以通過(guò)在 options 中添加 lazy 屬性來(lái)達(dá)到目的。在 effect 函數(shù)源碼中,判斷 options.lazy 選項(xiàng)的值,當(dāng)值為true 時(shí),則不立即執(zhí)行副作用函數(shù),從而實(shí)現(xiàn)懶執(zhí)行的 effect。

接著通過(guò) bind 函數(shù)返回一個(gè)新的副作用函數(shù)runner,這個(gè)新函數(shù)的this被指定為 _effect,并將 _effect 添加到這個(gè)新副作用函數(shù)的 effect 屬性上,最后返回這個(gè)新副作用函數(shù)。

由于 effect API 返回的是封裝后的副作用函數(shù),原始的副作用函數(shù)存儲(chǔ)在封裝后的副作用函數(shù)的effect屬性上,因此如果想要獲取用戶(hù)傳入的副作用函數(shù),需要通過(guò) fn.effect.fn 來(lái)獲取。

在 effect 函數(shù)中調(diào)用了 ReactiveEffect 類(lèi)創(chuàng)建副作用,接下來(lái)看看 ReactiveEffect 類(lèi)的實(shí)現(xiàn)。

ReactiveEffect 類(lèi)
// packages/reactivity/src/effect.ts

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

  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() {
    // 如果 effect 已停用,返回原始副作用函數(shù)執(zhí)行后的結(jié)果
    if (!this.active) {
      return this.fn()
    }
    let parent: ReactiveEffect | undefined = activeEffect
    let lastShouldTrack = shouldTrack
    while (parent) {
      if (parent === this) {
        return
      }
      parent = parent.parent
    }
    try {
      // 創(chuàng)建一個(gè)新的副作用前將當(dāng)前正在執(zhí)行的副作用存儲(chǔ)到新建的副作用的 parent 屬性上,解決嵌套effect 的情況
      this.parent = activeEffect
      // 將創(chuàng)建的副作用設(shè)置為當(dāng)前正則正在執(zhí)行的副作用   
      activeEffect = this
      // 將 shouldTrack 設(shè)置為 true,表示開(kāi)啟依賴(lài)收集
      shouldTrack = true

      trackOpBit = 1 << ++effectTrackDepth

      if (effectTrackDepth <= maxMarkerBits) {
        // 初始化依賴(lài)
        initDepMarkers(this)
      } else {
        // 清除依賴(lài)
        cleanupEffect(this)
      }
    //   返回原始副作用函數(shù)執(zhí)行后的結(jié)果
      return this.fn()
    } finally {
      if (effectTrackDepth <= maxMarkerBits) {
        finalizeDepMarkers(this)
      }

      trackOpBit = 1 << --effectTrackDepth

      // 重置當(dāng)前正在執(zhí)行的副作用   
      activeEffect = this.parent
      shouldTrack = lastShouldTrack
      this.parent = undefined
    }
  }
  // 停止(清除) effect
  stop() {
    if (this.active) {
      cleanupEffect(this)
      if (this.onStop) {
        this.onStop()
      }
      this.active = false
    }
  }
}

在 ReactiveEffect 類(lèi)中,定義了一個(gè) run 方法,這個(gè) run 方法就是創(chuàng)建副作用時(shí)實(shí)際運(yùn)行方法。每次派發(fā)更新時(shí),都會(huì)執(zhí)行這個(gè)run方法,從而更新值。

全局變量 activeEffect 用來(lái)維護(hù)當(dāng)前正在執(zhí)行的副作用,當(dāng)存在嵌套渲染組件的時(shí)候,依賴(lài)收集后,副作用函數(shù)會(huì)被覆蓋,即 activeEffect 存儲(chǔ)的副作用函數(shù)在嵌套 effect 的時(shí)候會(huì)被內(nèi)層的副作用函數(shù)覆蓋。為了解決這個(gè)問(wèn)題,在 run 方法中,將當(dāng)前正在執(zhí)行的副作用activeEffect保存到新建的副作用的 parent 屬性上,然后再將新建的副作用設(shè)置為當(dāng)前正在執(zhí)行的副作用。在新建的副作用執(zhí)行完畢后,再將存儲(chǔ)到 parent 屬性的副作用重新設(shè)置為當(dāng)前正在執(zhí)行的副作用。

在 ReactiveEffect 類(lèi)中,還定義了一個(gè) stop 方法,該方法用來(lái)停止并清除當(dāng)前正在執(zhí)行的副作用。

track 收集依賴(lài)

當(dāng)使用代理對(duì)象訪(fǎng)問(wèn)對(duì)象的屬性時(shí),就會(huì)觸發(fā)代理對(duì)象的 get 攔截函數(shù)執(zhí)行,如下面的代碼所示:

const obj = { foo: 1 }

const p = new Proxy(obj, {
  get(target, key, receiver) {
    track(target, key)
    return Reflect.get(target, key, receiver)
  }
}) 

p.foo

在上面的代碼中,通過(guò)代理對(duì)象p 訪(fǎng)問(wèn) foo 屬性,便會(huì)觸發(fā) get 攔截函數(shù)的執(zhí)行,此時(shí)就在 get 攔截函數(shù)中調(diào)用 track 函數(shù)進(jìn)行依賴(lài)收集。源碼中 get 攔截函數(shù)的解析可閱讀《Vue3 源碼解讀之非原始值的響應(yīng)式原理》一文中的「訪(fǎng)問(wèn)屬性的攔截」小節(jié)。

下面,我們來(lái)看看 track 函數(shù)的實(shí)現(xiàn)。

track 函數(shù)
// packages/reactivity/src/effect.ts

// 收集依賴(lài)
export function track(target: object, type: TrackOpTypes, key: unknown) {
    // 如果開(kāi)啟了依賴(lài)收集并且有正在執(zhí)行的副作用,則收集依賴(lài)
  if (shouldTrack && activeEffect) {
    // 在 targetMap 中獲取對(duì)應(yīng)的 target 的依賴(lài)集合
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      // 如果 target 不在 targetMap 中,則加入,并初始化 value 為 new Map()
      targetMap.set(target, (depsMap = new Map()))
    }
    // 從依賴(lài)集合中獲取對(duì)應(yīng)的 key 的依賴(lài)
    let dep = depsMap.get(key)
    if (!dep) {
      // 如果 key 不存在,將這個(gè) key 作為依賴(lài)收集起來(lái),并初始化 value 為 new Set()
      depsMap.set(key, (dep = createDep()))
    }

    const eventInfo = __DEV__
      ? { effect: activeEffect, target, type, key }
      : undefined

    trackEffects(dep, eventInfo)
  }
}

在 track 函數(shù)中,通過(guò)一個(gè) if 語(yǔ)句判斷是否進(jìn)行依賴(lài)收集,只有當(dāng) shouldTrack 為 true 并且存在 activeEffect,即開(kāi)啟了依賴(lài)收集并且存在正在執(zhí)行的副作用時(shí),才進(jìn)行依賴(lài)收集。

然后通過(guò) target 對(duì)象從 targetMap 中嘗試獲取對(duì)應(yīng) target 的依賴(lài)集合depsMap,如果 targetMap 中不存在當(dāng)前target的依賴(lài)集合,則將當(dāng)前 target 添加進(jìn) targetMap 中,并將 targetMap 的 value 初始化為 new Map()。

// 在 targetMap 中獲取對(duì)應(yīng)的 target 的依賴(lài)集合
let depsMap = targetMap.get(target)
if (!depsMap) {
  // 如果 target 不在 targetMap 中,則加入,并初始化 value 為 new Map()
  targetMap.set(target, (depsMap = new Map()))
}

接著根據(jù)target中被讀取的 key,從依賴(lài)集合depsMap中獲取對(duì)應(yīng) key 的依賴(lài),如果依賴(lài)不存在,則將這個(gè) key 的依賴(lài)收集到依賴(lài)集合depsMap中,并將依賴(lài)初始化為 new Set()。

// 從依賴(lài)集合中獲取對(duì)應(yīng)的 key 的依賴(lài)
let dep = depsMap.get(key)
if (!dep) {
  // 如果 key 不存在,將這個(gè) key 作為依賴(lài)收集起來(lái),并初始化 value 為 new Set()
  depsMap.set(key, (dep = createDep()))
}

最后調(diào)用 trackEffects 函數(shù),將副作用函數(shù)收集到依賴(lài)集合depsMap中。

const eventInfo = __DEV__
  ? { effect: activeEffect, target, type, key }
  : undefined

trackEffects(dep, eventInfo)
trackEffects 函數(shù)
// 收集副作用函數(shù)
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.
    // 如果依賴(lài)中并不存當(dāng)前的 effect 副作用函數(shù)
    shouldTrack = !dep.has(activeEffect!)
  }

  if (shouldTrack) {
    // 將當(dāng)前的副作用函數(shù)收集進(jìn)依賴(lài)中
    dep.add(activeEffect!)
    // 并在當(dāng)前副作用函數(shù)的 deps 屬性中記錄該依賴(lài)
    activeEffect!.deps.push(dep)
    if (__DEV__ && activeEffect!.onTrack) {
      activeEffect!.onTrack(
        Object.assign(
          {
            effect: activeEffect!
          },
          debuggerEventExtraInfo
        )
      )
    }
  }
}

在 trackEffects 函數(shù)中,檢查當(dāng)前正在執(zhí)行的副作用函數(shù) activeEffect 是否已經(jīng)被收集到依賴(lài)集合中,如果沒(méi)有,就將當(dāng)前的副作用函數(shù)收集到依賴(lài)集合中。同時(shí)在當(dāng)前副作用函數(shù)的 deps 屬性中記錄該依賴(lài)。

trigger 派發(fā)更新

當(dāng)對(duì)屬性進(jìn)行賦值時(shí),會(huì)觸發(fā)代理對(duì)象的 set 攔截函數(shù)執(zhí)行,如下面的代碼所示:

const obj = { foo: 1 }

const p = new Proxy(obj, {
  // 攔截設(shè)置操作
  set(target, key, newVal, receiver){
    // 如果屬性不存在,則說(shuō)明是在添加新屬性,否則設(shè)置已有屬性
    const type = Object.prototype.hasOwnProperty.call(target,key) ?  'SET' : 'ADD'
    
    // 設(shè)置屬性值
    const res = Reflect.set(target, key, newVal, receiver)
    // 把副作用函數(shù)從桶里取出并執(zhí)行,將 type 作為第三個(gè)參數(shù)傳遞給 trigger 函數(shù)
    trigger(target,key,type)
    
    return res
  }
  
  // 省略其他攔截函數(shù)
})

p.foo = 2

在上面的代碼中,通過(guò)代理對(duì)象p 訪(fǎng)問(wèn) foo 屬性,便會(huì)觸發(fā) set 攔截函數(shù)的執(zhí)行,此時(shí)就在 set 攔截函數(shù)中調(diào)用 trigger 函數(shù)中派發(fā)更新。源碼中 set 攔截函數(shù)的解析可閱讀《Vue3 源碼解讀之非原始值的響應(yīng)式原理》一文中的「設(shè)置屬性操作的攔截」小節(jié)。

下面,我們來(lái)看看 track 函數(shù)的實(shí)現(xiàn)。

trigger 函數(shù)

trigger 函數(shù)的源碼如下:

export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  // 該 target 從未被追蹤,則不繼續(xù)執(zhí)行
  if (!depsMap) {
    // never been tracked
    return
  }

  // 存放所有需要派發(fā)更新的副作用函數(shù)  
  let deps: (Dep | undefined)[] = []
  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared
    // trigger all effects for target
    // 當(dāng)需要清除依賴(lài)時(shí),將當(dāng)前 target 的依賴(lài)全部傳入
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {
    // 處理數(shù)組的特殊情況
    depsMap.forEach((dep, key) => {
      // 如果對(duì)應(yīng)的長(zhǎng)度, 有依賴(lài)收集需要更新
      if (key === 'length' || key >= (newValue as number)) {
        deps.push(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    // 在 SET | ADD | DELETE 的情況,添加當(dāng)前 key 的依賴(lài)
    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)) {
            // 操作類(lèi)型為 ADD 時(shí)觸發(fā)Map 數(shù)據(jù)結(jié)構(gòu)的 keys 方法的副作用函數(shù)重新執(zhí)行
            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)) {
            // 操作類(lèi)型為 DELETE 時(shí)觸發(fā)Map 數(shù)據(jù)結(jié)構(gòu)的 keys 方法的副作用函數(shù)重新執(zhí)行
            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[] = []
    // 將需要執(zhí)行的副作用函數(shù)收集到 effects 數(shù)組中
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep)
      }
    }
    if (__DEV__) {
      triggerEffects(createDep(effects), eventInfo)
    } else {
      triggerEffects(createDep(effects))
    }
  }
}

在 trigger 函數(shù)中,首先檢查當(dāng)前 target 是否有被追蹤,如果從未被追蹤過(guò),即target的依賴(lài)未被收集,則不需要執(zhí)行派發(fā)更新,直接返回即可。

const depsMap = targetMap.get(target)
// 該 target 從未被追蹤,則不繼續(xù)執(zhí)行
if (!depsMap) {
  // never been tracked
  return
}

接著創(chuàng)建一個(gè) Set 類(lèi)型的 deps 集合,用來(lái)存儲(chǔ)當(dāng)前target的這個(gè) key 所有需要執(zhí)行派發(fā)更新的副作用函數(shù)。

// 存放所有需要派發(fā)更新的副作用函數(shù)  
let deps: (Dep | undefined)[] = []

接下來(lái)就根據(jù)操作類(lèi)型type 和 key 來(lái)收集需要執(zhí)行派發(fā)更新的副作用函數(shù)。

如果操作類(lèi)型是 TriggerOpTypes.CLEAR ,那么表示需要清除所有依賴(lài),將當(dāng)前target的所有副作用函數(shù)添加到 deps 集合中。

if (type === TriggerOpTypes.CLEAR) {
  // collection being cleared
  // trigger all effects for target
  // 當(dāng)需要清除依賴(lài)時(shí),將當(dāng)前 target 的依賴(lài)全部傳入
  deps = [...depsMap.values()]
}

如果操作目標(biāo)是數(shù)組,并且修改了數(shù)組的 length 屬性,需要把與 length 屬性相關(guān)聯(lián)的副作用函數(shù)以及索引值大于或等于新的 length 值元素的相關(guān)聯(lián)的副作用函數(shù)從 depsMap 中取出并添加到 deps 集合中。

else if (key === 'length' && isArray(target)) {
  // 如果操作目標(biāo)是數(shù)組,并且修改了數(shù)組的 length 屬性
  depsMap.forEach((dep, key) => {
    // 對(duì)于索引大于或等于新的 length 值的元素,
    // 需要把所有相關(guān)聯(lián)的副作用函數(shù)取出并添加到 deps 中執(zhí)行
    if (key === 'length' || key >= (newValue as number)) {
      deps.push(dep)
    }
  })
}

如果當(dāng)前的 key 不為 undefined,則將與當(dāng)前key相關(guān)聯(lián)的副作用函數(shù)添加到 deps 集合中。注意這里的判斷條件 void 0,是通過(guò) void 運(yùn)算符的形式表示 undefined 。

if (key !== void 0) {
  deps.push(depsMap.get(key))
}

接下來(lái)通過(guò) Switch 語(yǔ)句來(lái)收集操作類(lèi)型為 ADD、DELETE、SET 時(shí)與 ITERATE_KEY 和 MAP_KEY_ITERATE_KEY 相關(guān)聯(lián)的副作用函數(shù)。

// 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)) {
        // 操作類(lèi)型為 ADD 時(shí)觸發(fā)Map 數(shù)據(jù)結(jié)構(gòu)的 keys 方法的副作用函數(shù)重新執(zhí)行
        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)) {
        // 操作類(lèi)型為 DELETE 時(shí)觸發(fā)Map 數(shù)據(jù)結(jié)構(gòu)的 keys 方法的副作用函數(shù)重新執(zhí)行
        deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
      }
    }
    break
  case TriggerOpTypes.SET:
    if (isMap(target)) {
      deps.push(depsMap.get(ITERATE_KEY))
    }
    break
}

最后調(diào)用 triggerEffects 函數(shù),傳入收集的副作用函數(shù),執(zhí)行派發(fā)更新。

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[] = []
  // 將需要執(zhí)行的副作用函數(shù)收集到 effects 數(shù)組中
  for (const dep of deps) {
    if (dep) {
      effects.push(...dep)
    }
  }
  if (__DEV__) {
    triggerEffects(createDep(effects), eventInfo)
  } else {
    triggerEffects(createDep(effects))
  }
}
triggerEffects 函數(shù)
export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // spread into array for stabilization
  // 遍歷需要執(zhí)行的副作用函數(shù)集合   
  for (const effect of isArray(dep) ? dep : [...dep]) {
    // 如果 trigger 觸發(fā)執(zhí)行的副作用函數(shù)與當(dāng)前正在執(zhí)行的副作用函數(shù)相同,則不觸發(fā)執(zhí)行
    if (effect !== activeEffect || effect.allowRecurse) {
      if (__DEV__ && effect.onTrigger) {
        effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
      }
      if (effect.scheduler) {
        // 如果一個(gè)副作用函數(shù)存在調(diào)度器,則調(diào)用該調(diào)度器
        effect.scheduler()
      } else {
        // 否則直接執(zhí)行副作用函數(shù)
        effect.run()
      }
    }
  }
}

在 triggerEffects 函數(shù)中,遍歷需要執(zhí)行的副作用函數(shù)集合,如果當(dāng)前副作用函數(shù)存在調(diào)度器,則執(zhí)行該調(diào)度器,否則直接執(zhí)行該副作用函數(shù)的 run 方法,執(zhí)行更新。

到此,相信大家對(duì)“Vue3副作用函數(shù)與依賴(lài)收集怎么實(shí)現(xiàn)”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI