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ù)來(lái)執(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è)新桶來(lái)存儲(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) {
// 沒(méi)有正在執(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í)行順序,通過(guò)調(diào)度器還可以做到控制它的執(zhí)行次數(shù)
- 正常的打印結(jié)果應(yīng)該是
1,2,3,但是我們只關(guān)心執(zhí)行的最后結(jié)果,應(yīng)該拿到的是1,3,執(zhí)行三次有些多余,這時(shí),就需要使用到調(diào)度器來(lái)修改執(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)不包含過(guò)渡階段,使用調(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ā)過(guò)程中面臨了年份和月份下拉框的實(shí)現(xiàn)、周期顯示以及可編輯日歷的樣式和數(shù)據(jù)獲取等挑戰(zhàn),感興趣的朋友一起看看吧2025-01-01
一文解決vue2 element el-table自適應(yīng)高度問(wèn)題
在寫公司后臺(tái)項(xiàng)目的時(shí)候遇到一個(gè)需求,要求表格頁(yè)面不能有滾動(dòng)條,所以必須封裝一個(gè)公共方法來(lái)實(shí)現(xiàn)表格自適應(yīng)高度,本問(wèn)小編給大家介紹了如何解決vue2 element el-table自適應(yīng)高度問(wèn)題,需要的朋友可以參考下2023-11-11

