您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“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); }
代碼實現完成,那接下來看一下單測結果。
好,現在我們再回到最初的栗子????,在上面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了,這樣我們就實現了期望的輸出。
再跑一遍完整流程,來看一下單測結果,確保新增代碼不影響以往功能。
測試結束完以后,由于job queue
是一個實際案例單測,所以我們將其抽離到examples
下面的testCase
里,建立jobQueue.spec.ts
。
讀到這里,這篇“vue3調度器effect的scheduler功能怎么實現”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。