欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue3調(diào)度器effect的scheduler功能實(shí)現(xiàn)詳解

 更新時(shí)間:2022年12月06日 16:01:54   作者:IamZJT_  
這篇文章主要為大家介紹了vue3調(diào)度器effect的scheduler功能實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

一、調(diào)度執(zhí)行

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

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

有了調(diào)度函數(shù),我們?cè)?code>trigger函數(shù)中觸發(fā)副作用函數(shù)重新執(zhí)行時(shí),就可以直接調(diào)用用戶傳遞的調(diào)度器函數(shù),從而把控制權(quán)交給用戶。

舉個(gè)栗子??:

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

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

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

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

   1
   2
   3

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

   1
   3

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

二、單元測(cè)試

首先還是藉由單測(cè)來梳理一下功能,這是直接從vue3源碼中粘貼過來對(duì)scheduler的單測(cè),里面很詳細(xì)的描述了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);
});

大概介紹一下這個(gè)單測(cè)的流程:

  • 通過 effect 的第二個(gè)參數(shù)給定的一個(gè)對(duì)象 { scheduler: () => {} }, 屬性是scheduler, 值是一個(gè)函數(shù);
  • effect 第一次執(zhí)行的時(shí)候, 還是會(huì)執(zhí)行 fn;
  • 當(dāng)響應(yīng)式對(duì)象被 set,也就是數(shù)據(jù) update 時(shí), 如果 scheduler 存在, 則不會(huì)執(zhí)行 fn, 而是執(zhí)行 scheduler;
  • 當(dāng)再次執(zhí)行 runner 的時(shí)候, 才會(huì)再次的執(zhí)行 fn.

三、代碼實(shí)現(xiàn)

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

class ReactiveEffect {
  private _fn: any;

  // + 接收scheduler
  // + 在構(gòu)造函數(shù)的參數(shù)上使用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);
}

代碼實(shí)現(xiàn)完成,那接下來看一下單測(cè)結(jié)果。

四、回歸實(shí)現(xiàn)

好,現(xiàn)在我們?cè)倩氐阶畛醯睦踝??,在上面scheduler基礎(chǔ)上,完成現(xiàn)有需求,繼續(xù)看一下對(duì)此需求的單測(cè)。

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

  // 一個(gè)標(biāo)志代表是否正在刷新隊(duì)列
  let isFlushing = false;

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

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

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

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

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

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

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

再觀察上面的單測(cè)代碼,首先,我們定義了一個(gè)任務(wù)隊(duì)列jobQueue,它是一個(gè)Set數(shù)據(jù)結(jié)構(gòu),目的是利用Set數(shù)據(jù)結(jié)構(gòu)的自動(dòng)去重功能。

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

然后我們把目光轉(zhuǎn)向flushJob函數(shù),該函數(shù)通過isFlushing標(biāo)志判斷是否需要執(zhí)行,只有當(dāng)其為false 時(shí)才需要執(zhí)行,而一旦flushJob函數(shù)開始執(zhí)行,isFlushing標(biāo)志就會(huì)設(shè)置為true,意思是無論調(diào)用多少次flushJob函數(shù),在一個(gè)周期內(nèi)都只會(huì)執(zhí)行一次。

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

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

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

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

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

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

五、結(jié)語(yǔ)

可能你已經(jīng)注意到了,這個(gè)功能點(diǎn)類似于在Vue.js中連續(xù)多次修改響應(yīng)式數(shù)據(jù)但只會(huì)觸發(fā)一次更新,實(shí)際上Vue.js內(nèi)部實(shí)現(xiàn)了一個(gè)更加完善的調(diào)度器,思路與上文介紹的相同。

此外,綜合前面的這些內(nèi)容,我們就可以實(shí)現(xiàn)Vue.js中一個(gè)非常重要且非常有特色的能力:computed計(jì)算屬性,這個(gè)就后面再慢慢實(shí)現(xiàn)吧...

以上就是vue3調(diào)度器effect的scheduler功能實(shí)現(xiàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于vue3調(diào)度器effect scheduler的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論