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

溫馨提示×

溫馨提示×

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

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

vue3調度器effect的scheduler功能怎么實現

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

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

一、調度執行

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

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

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

舉個栗子:

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

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

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

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

   1
   2
   3

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

   1
   3

那么就考慮傳入調度器函數去幫助我們實現此功能,那由此需求,我們先來實現一下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 第一次執行的時候, 還是會執行 fn;

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

  • 當再次執行 runner 的時候, 才會再次的執行 fn.

三、代碼實現

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

class ReactiveEffect {
  private _fn: any;

  // + 接收scheduler
  // + 在構造函數的參數上使用public等同于創建了同名的成員變量
  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);
}

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

  for (const effect of dep) {
    // + 判斷是否有scheduler, 有則執行,無則執行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);
}

代碼實現完成,那接下來看一下單測結果。

vue3調度器effect的scheduler功能怎么實現

四、回歸實現

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

it('job queue', () => {
  // 定義一個任務隊列
  const jobQueue = new Set();
  // 使用 Promise.resolve() 創建一個 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執行兩次,但是由于是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中,調整一下遍歷執行,為了讓我們的scheduler能拿到原始依賴。

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

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

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

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

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

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

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

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

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

vue3調度器effect的scheduler功能怎么實現

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

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

向AI問一下細節

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

AI

永丰县| 崇义县| 菏泽市| 县级市| 南投市| 礼泉县| 新余市| 芮城县| 灵山县| 丰镇市| 洪江市| 延庆县| 郑州市| 嘉鱼县| 天水市| 柘荣县| 东兴市| 长武县| 六枝特区| 咸阳市| 灵宝市| 大足县| 砚山县| 新竹市| 贡嘎县| 资中县| 临朐县| 奉新县| 枣强县| 沧源| 绵阳市| 方城县| 遂平县| 凤阳县| 东丽区| 江孜县| 尤溪县| 湘西| 济源市| 礼泉县| 石家庄市|