Vue.js設計與實現學習總結(第四章5)排程執行

瑪拉-以琳發表於2022-12-17
排程執行是指當 trigger 動作觸發副作用函式重新執行的時候, 可以決定函式執行的時機, 次數和方式

以下面程式碼為例

const data = { foo: 1 }
const obj = new Proxy(data, /*----*/)

effect(() => {
  console.log(obj.foo)
})

obj.foo++

console.log('結束了')

程式碼輸出為: 1 2 結束了

若此時需要將列印順序調整為: 1 結束了 2 並且需要在不調整程式碼的情況下實現改需求就需要響應系統支援排程.可以為effect函式設計一個選項引數options, 允許使用者指定排程器:

effect (
  () => {
  console.log(obj.foo)
  },
  {
    // 排程器 scheduler 是一個函式
    scheduler (fn) {
      //  ...
    }
  }
)

因此在副作用函式中也需要掛載options:

function effect (fn, options = {}) {
  const effectFn = () => {
    cleanup(effectFn)
    // 當呼叫 effect 註冊副作用函式時, 將副作用函式複製給 activeEffect
    activeEffect = effectFn
    // 在呼叫副作用函式前將函式壓棧
    effectStack.push(effectFn)
    fn()
    // 當前副作用函式執行結束後出棧並把activeEffect還原為之前的值
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
  }
  // 將 options 掛載到effectFn上
  effectFn.options = options
  // activeEffect.deps 用於儲存所有與副作用函式相關的依賴集合
  effectFn.deps = []
  // 執行副作用函式
  effectFn()
}

trigger函式中的副作用函式重新執行時, 就可以直接呼叫使用者傳遞的排程器函式將控制權交給使用者:

function trigger (target, key) {
  const depsMap = bucket.get(target)
  if (!depsMap) return
  const effects = depsMap.get(key)

  const effectsToRun = new Set()
  effects && effects.forEach(effectFn => {
    // 如果 trigger 觸發執行的副作用函式與當前正在執行的函式相同則不觸發執行
    if (effectFn !== activeEffect) {
      effectsToRun.add(effectFn)
    }
  })

  effectsToRun.forEach(effectFn => {
    // 如果一個副作用函式有排程器則呼叫改排程器, 並將副作用函式作為引數傳遞
    if (effectFn.options.scheduler) {
      effectFn.options.scheduler(effectFn)
    } else {
      // 否則直接執行副作用函式
      effectFn()
    }
  })
}

相關文章