溫馨提示×

溫馨提示×

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

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

vue3調度器effect的scheduler功能怎么實現(xiàn)

發(fā)布時間:2022-12-07 09:52:21 來源:億速云 閱讀:126 作者:iii 欄目:開發(fā)技術

本文小編為大家詳細介紹“vue3調度器effect的scheduler功能怎么實現(xiàn)”,內容詳細,步驟清晰,細節(jié)處理妥當,希望這篇“vue3調度器effect的scheduler功能怎么實現(xiàn)”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。

一、調度執(zhí)行

說到scheduler,也就是vue3的調度器,可能大家還不是特別明白調度器的是什么,先大概介紹一下。

可調度性是響應式系統(tǒng)非常重要的特性。首先我們要明確什么是可調度性。所謂可調度性,指的是當trigger 動作觸發(fā)副作用函數重新執(zhí)行時,有能力決定副作用函數執(zhí)行的時機、次數以及方式。

有了調度函數,我們在trigger函數中觸發(fā)副作用函數重新執(zhí)行時,就可以直接調用用戶傳遞的調度器函數,從而把控制權交給用戶。

舉個栗子:

const obj = reactive({ foo: 1 });

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

obj.foo++;
obj.foo++;

首先在副作用函數中打印obj.foo的值,接著連續(xù)對其執(zhí)行兩次自增操作,輸出如下:

   1
   2
   3

由輸出結果可知,obj.foo的值一定會從1自增到3,2只是它的過渡狀態(tài)。如果我們只關心最終結果而不關心過程,那么執(zhí)行三次打印操作是多余的,我們期望的打印結果是:

   1
   3

那么就考慮傳入調度器函數去幫助我們實現(xiàn)此功能,那由此需求,我們先來實現(xiàn)一下scheduler功能。

二、單元測試

首先還是藉由單測來梳理一下功能,這是直接從vue3源碼中粘貼過來對scheduler的單測,里面很詳細的描述了scheduler的功能。

it('scheduler', () => {
  let dummy;
  let run: any;
  const scheduler = jest.fn(() => {
    run = runner;
  });
  const obj = reactive({ foo: 1 });
  const runner = effect(
    () => {
      dummy = obj.foo;
    },
    { scheduler },
  );
  expect(scheduler).not.toHaveBeenCalled();
  expect(dummy).toBe(1);
  // should be called on first trigger
  obj.foo++;
  expect(scheduler).toHaveBeenCalledTimes(1);
  // should not run yet
  expect(dummy).toBe(1);
  // manually run
  run();
  // should have run
  expect(dummy).toBe(2);
});

大概介紹一下這個單測的流程:

  • 通過 effect 的第二個參數給定的一個對象 { scheduler: () => {} }, 屬性是scheduler, 值是一個函數;

  • effect 第一次執(zhí)行的時候, 還是會執(zhí)行 fn;

  • 當響應式對象被 set,也就是數據 update 時, 如果 scheduler 存在, 則不會執(zhí)行 fn, 而是執(zhí)行 scheduler;

  • 當再次執(zhí)行 runner 的時候, 才會再次的執(zhí)行 fn.

三、代碼實現(xiàn)

那接下來就直接開始代碼實現(xiàn)功能,這里直接貼出完整代碼了,// + 會標注出新增加的代碼。

class ReactiveEffect {
  private _fn: any;

  // + 接收scheduler
  // + 在構造函數的參數上使用public等同于創(chuàng)建了同名的成員變量
  constructor(fn, public scheduler?) {
    this._fn = fn;
  }

  run() {
    activeEffect = this;
    return this._fn();
  }
}

// * ============================== ↓ 依賴收集 track ↓ ============================== * //
// * targetMap: target -> key
const targetMap = new WeakMap();

// * target -> key -> dep
export function track(target, key) {
  // * depsMap: key -> dep
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }

  // * dep
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }

  dep.add(activeEffect);
}

// * ============================== ↓ 觸發(fā)依賴 trigger ↓ ============================== * //
export function trigger(target, key) {
  let depsMap = targetMap.get(target);
  let dep = depsMap.get(key);

  for (const effect of dep) {
    // + 判斷是否有scheduler, 有則執(zhí)行,無則執(zhí)行fn
    if (effect.scheduler) {
      effect.scheduler();
    } else {
      effect.run();
    }
  }
}

let activeEffect;

export function effect(fn, options: any = {}) {
  // + 直接將scheduler掛載到依賴上
  const _effect = new ReactiveEffect(fn, options.scheduler);

  _effect.run();

  return _effect.run.bind(_effect);
}

代碼實現(xiàn)完成,那接下來看一下單測結果。

vue3調度器effect的scheduler功能怎么實現(xiàn)

四、回歸實現(xiàn)

好,現(xiàn)在我們再回到最初的栗子????,在上面scheduler基礎上,完成現(xiàn)有需求,繼續(xù)看一下對此需求的單測。

it('job queue', () => {
  // 定義一個任務隊列
  const jobQueue = new Set();
  // 使用 Promise.resolve() 創(chuàng)建一個 Promise 實例,我們用它將一個任務添加到微任務隊列
  const p = Promise.resolve();

  // 一個標志代表是否正在刷新隊列
  let isFlushing = false;

  function flushJob() {
    // 如果隊列正在刷新,則什么都不做
    if (isFlushing) return;
    // 設置為true,代表正在刷新
    isFlushing = true;
    // 在微任務隊列中刷新 jobQueue 隊列
    p.then(() => {
      jobQueue.forEach((job: any) => job());
    }).finally(() => {
      // 結束后重置 isFlushing
      isFlushing = false;
      // 雖然scheduler執(zhí)行兩次,但是由于是Set,所以只有一項
      expect(jobQueue.size).toBe(1);
      // 期望最終結果拿數組存儲后進行斷言
      expect(logArr).toEqual([1, 3]);
    });
  }

  const obj = reactive({ foo: 1 });
  let logArr: number[] = [];

  effect(
    () => {
      logArr.push(obj.foo);
    },
    {
      scheduler(fn) {
        // 每次調度時,將副作用函數添加到 jobQueue 隊列中
        jobQueue.add(fn);
        // 調用 flushJob 刷新隊列
        flushJob();
      },
    },
  );

  obj.foo++;
  obj.foo++;

  expect(obj.foo).toBe(3);
});

在分析上段代碼之前,為了輔助完成上述功能,我們需要回到trigger中,調整一下遍歷執(zhí)行,為了讓我們的scheduler能拿到原始依賴。

for (const effect of dep) {
  // + 判斷是否有scheduler, 有則執(zhí)行,無則執(zhí)行fn
  if (effect.scheduler) {
    effect.scheduler(effect._fn);
  } else {
    effect.run();
  }
}

再觀察上面的單測代碼,首先,我們定義了一個任務隊列jobQueue,它是一個Set數據結構,目的是利用Set數據結構的自動去重功能。

接著我們看調度器scheduler的實現(xiàn),在每次調度執(zhí)行時,先將當前副作用函數添加到jobQueue隊列中,再調用flushJob函數刷新隊列。

然后我們把目光轉向flushJob函數,該函數通過isFlushing標志判斷是否需要執(zhí)行,只有當其為false 時才需要執(zhí)行,而一旦flushJob函數開始執(zhí)行,isFlushing標志就會設置為true,意思是無論調用多少次flushJob函數,在一個周期內都只會執(zhí)行一次。

需要注意的是,在flushJob內通過p.then將一個函數添加到微任務隊列,在微任務隊列內完成對jobQueue的遍歷執(zhí)行。

整段代碼的效果是,連續(xù)對obj.foo執(zhí)行兩次自增操作,會同步且連續(xù)地執(zhí)行兩次scheduler調度函數,這意味著同一個副作用函數會被jobQueue.add(fn)添加兩次,但由于Set數據結構的去重能力,最終jobQueue中只會有一項,即當前副作用函數。

類似地,flushJob也會同步且連續(xù)執(zhí)行兩次,但由于isFlushing標志的存在,實際上flushJob函數在一個事件循環(huán)內只會執(zhí)行一次,即在微任務隊列內執(zhí)行一次。

當微任務隊列開始執(zhí)行時,就會遍歷jobQueue并執(zhí)行里面存儲的副作用函數。由于此時jobQueue隊列內只有一個副作用函數,所以只會執(zhí)行一次,并且當它執(zhí)行時,字段obj.foo的值已經是3了,這樣我們就實現(xiàn)了期望的輸出。

再跑一遍完整流程,來看一下單測結果,確保新增代碼不影響以往功能。

vue3調度器effect的scheduler功能怎么實現(xiàn)

測試結束完以后,由于job queue是一個實際案例單測,所以我們將其抽離到examples下面的testCase里,建立jobQueue.spec.ts。

讀到這里,這篇“vue3調度器effect的scheduler功能怎么實現(xiàn)”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

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

AI