vue3調(diào)度器scheduler功能和用法詳解
調(diào)度器
是vue3響應(yīng)式系統(tǒng)中一個(gè)非常重要的特性,可調(diào)度性指的是當(dāng)trigger
動(dòng)作觸發(fā)副作用函數(shù)重新執(zhí)行時(shí),有能力決定副作用函數(shù)執(zhí)行的時(shí)機(jī)、次數(shù)以及方式
const data = { foo: 1 } const obj = new Proxy(data, { /* ... */ }) // 上文中的響應(yīng)式 effect(() => { console.log(obj.foo) }) obj.foo++ console.log('結(jié)束了')
正常執(zhí)行結(jié)果順序是1,2,結(jié)束了
,但是,若我們期望的打印順序發(fā)生改變1,結(jié)束了,2
,要實(shí)現(xiàn)這樣的打印結(jié)果,就需要使用到調(diào)度器
- 可以為函數(shù)effect函數(shù)設(shè)計(jì)一個(gè)選項(xiàng)參數(shù)
options
,允許用戶指定調(diào)度器:
effect( () => { console.log(obj.foo); }, // options { // 調(diào)度器 scheduler 是一個(gè)函數(shù) scheduler(fn) { // ... }, });
- 將調(diào)度器對(duì)象掛在到當(dāng)前副作用函數(shù)中
// effect 函數(shù)用于注冊(cè)副作用函數(shù) function effect(fn, options = {}) { fn.options = options; // 新增掛在調(diào)度器 activeEffect = fn; // 執(zhí)行副作用函數(shù) fn(); }
- 修改參數(shù)時(shí),判斷是否存在調(diào)度器,存在,執(zhí)行當(dāng)前掛載中調(diào)度器方法
(在taigger函數(shù)中)
function trigger(target, key) { // 根據(jù)target從桶中取得depsMap,它是key --> effects const depsMap = bucket.get(target); if (!depsMap) return; // 根據(jù)key取得當(dāng)前對(duì)應(yīng)的副作用函數(shù) const effects = depsMap.get(key); // 執(zhí)行副作用函數(shù) effects && effects.forEach((fn) => { // fn() if (fn.options.scheduler) { // 新增 fn.options.scheduler(fn); } else { // 否則直接執(zhí)行副作用函數(shù)(之前的默認(rèn)行為) fn(); } }); }
- 使用
setTimeout
開啟一個(gè)宏任務(wù)來執(zhí)行副作用函數(shù) fn,這樣,就能改變執(zhí)行順序,拿到我們想要的執(zhí)行結(jié)果
function effect(fn, options = {}) { fn.options = options; // 新增 activeEffect = fn; // 執(zhí)行副作用函數(shù) fn(); } effect( () => { console.log(obj.foo); }, // options { // 調(diào)度器 scheduler 是一個(gè)函數(shù) scheduler(fn) { // 修改參數(shù),將副作用函數(shù)放在宏任務(wù)隊(duì)列中執(zhí)行 setTimeout(fn) }, } );
完整代碼:
const data = { foo: 1 }; // 用一個(gè)全局變量存儲(chǔ)被注冊(cè)的副作用函數(shù) let activeEffect; // 創(chuàng)建一個(gè)新桶來存儲(chǔ)副作用函數(shù),包含key和value const bucket = new WeakMap(); const obj = new Proxy(data, { get(target, key) { // target:當(dāng)前對(duì)象,key:觸發(fā)監(jiān)聽的key track(target, key); return target[key]; }, set(target, key, newVal) { target[key] = newVal; trigger(target, key); }, }); // track函數(shù) function track(target, key) { // 沒有正在執(zhí)行的副作用函數(shù) 直接返回 if (!activeEffect) return target[key]; // 從這個(gè)桶中取出一個(gè)Map類型(key -> value) let depsMap = bucket.get(target); // 不存在,則創(chuàng)建一個(gè)Map與target關(guān)聯(lián) if (!depsMap) { bucket.set(target, (depsMap = new Map())); } // 根據(jù)key判斷每個(gè)key上是否存在對(duì)應(yīng)的副作用函數(shù) let deps = depsMap.get(key); // 不存在,則新建一個(gè)new Set,并與key關(guān)聯(lián) if (!deps) { depsMap.set(key, (deps = new Set())); } // 最后將當(dāng)前激活的副作用函數(shù)添加到桶中 deps.add(activeEffect); } // trigger函數(shù) function trigger(target, key) { // 根據(jù)target從桶中取得depsMap,它是key --> effects const depsMap = bucket.get(target); if (!depsMap) return; // 根據(jù)key取得當(dāng)前對(duì)應(yīng)的副作用函數(shù) const effects = depsMap.get(key); // 執(zhí)行副作用函數(shù) effects && effects.forEach((fn) => { if (fn.options.scheduler) { fn.options.scheduler(fn); } else { // 否則直接執(zhí)行副作用函數(shù)(之前的默認(rèn)行為) fn(); } }); } // effect 函數(shù)用于注冊(cè)副作用函數(shù) function effect(fn, options = {}) { fn.options = options; // 新增 activeEffect = fn; // 執(zhí)行副作用函數(shù) fn(); } effect( () => { console.log(obj.foo); }, // options { // 調(diào)度器 scheduler 是一個(gè)函數(shù) scheduler(fn) { // 修改參數(shù),將副作用函數(shù)放在宏任務(wù)隊(duì)列中執(zhí)行 setTimeout(fn); }, } ); obj.foo++; console.log("結(jié)束了");
除了控制副作用函數(shù)的執(zhí)行順序,通過調(diào)度器
還可以做到控制它的執(zhí)行次數(shù)
- 正常的打印結(jié)果應(yīng)該是
1,2,3
,但是我們只關(guān)心執(zhí)行的最后結(jié)果,應(yīng)該拿到的是1,3
,執(zhí)行三次有些多余,這時(shí),就需要使用到調(diào)度器
來修改執(zhí)行次數(shù)
const data = { foo: 1 } const obj = new Proxy(data, { /* ... */ }) // 上文中的響應(yīng)式 effect(() => { console.log(obj.foo) }) obj.foo++ obj.foo++;
- 實(shí)現(xiàn)不包含過渡階段,使用調(diào)度器基于
promise
,可以直接修改當(dāng)前當(dāng)前調(diào)度函數(shù)
// 定義一個(gè)任務(wù)隊(duì)列,使用它自動(dòng)去重能力 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) => job()); }).finally(() => { // 結(jié)束后重置 isFlushing isFlushing = false; }); }
- 執(zhí)行調(diào)度函數(shù)
effect( () => { console.log(obj.foo); }, // options { // 調(diào)度器 scheduler 是一個(gè)函數(shù) scheduler(fn) { jobQueue.add(fn); // 調(diào)用 flushJob 刷新隊(duì)列 flushJob(); }, } ); obj.foo++; obj.foo++;
到此這篇關(guān)于vue3調(diào)度器scheduler功能和用法詳解的文章就介紹到這了,更多相關(guān)vue3調(diào)度器scheduler內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue3+vite+tdesign實(shí)現(xiàn)日歷式可編輯的排課班表填寫功能
本文介紹了如何使用Vue3和tdesign實(shí)現(xiàn)一個(gè)日歷式、可編輯的排班填寫功能,開發(fā)過程中面臨了年份和月份下拉框的實(shí)現(xiàn)、周期顯示以及可編輯日歷的樣式和數(shù)據(jù)獲取等挑戰(zhàn),感興趣的朋友一起看看吧2025-01-01一文解決vue2 element el-table自適應(yīng)高度問題
在寫公司后臺(tái)項(xiàng)目的時(shí)候遇到一個(gè)需求,要求表格頁面不能有滾動(dòng)條,所以必須封裝一個(gè)公共方法來實(shí)現(xiàn)表格自適應(yīng)高度,本問小編給大家介紹了如何解決vue2 element el-table自適應(yīng)高度問題,需要的朋友可以參考下2023-11-11