vue3调度器scheduler功能和用法详解
作者:包子爱吃肉
调度器是vue3响应式系统中一个非常重要的特性,可调度性指的是当trigger 动作触发副作用函数重新执行时,有能力决定副作用函数执行的时机、次数以及方式,本文通过代码示例给大家介绍调度器是什么,有什么功能,怎么使用,感兴趣的同学可以借鉴阅读
调度器
是vue3响应式系统中一个非常重要的特性,可调度性指的是当trigger
动作触发副作用函数重新执行时,有能力决定副作用函数执行的时机、次数以及方式
const data = { foo: 1 } const obj = new Proxy(data, { /* ... */ }) // 上文中的响应式 effect(() => { console.log(obj.foo) }) obj.foo++ console.log('结束了')
正常执行结果顺序是1,2,结束了
,但是,若我们期望的打印顺序发生改变1,结束了,2
,要实现这样的打印结果,就需要使用到调度器
- 可以为函数effect函数设计一个选项参数
options
,允许用户指定调度器:
effect( () => { console.log(obj.foo); }, // options { // 调度器 scheduler 是一个函数 scheduler(fn) { // ... }, });
- 将调度器对象挂在到当前副作用函数中
// effect 函数用于注册副作用函数 function effect(fn, options = {}) { fn.options = options; // 新增挂在调度器 activeEffect = fn; // 执行副作用函数 fn(); }
- 修改参数时,判断是否存在调度器,存在,执行当前挂载中调度器方法
(在taigger函数中)
function trigger(target, key) { // 根据target从桶中取得depsMap,它是key --> effects const depsMap = bucket.get(target); if (!depsMap) return; // 根据key取得当前对应的副作用函数 const effects = depsMap.get(key); // 执行副作用函数 effects && effects.forEach((fn) => { // fn() if (fn.options.scheduler) { // 新增 fn.options.scheduler(fn); } else { // 否则直接执行副作用函数(之前的默认行为) fn(); } }); }
- 使用
setTimeout
开启一个宏任务来执行副作用函数 fn,这样,就能改变执行顺序,拿到我们想要的执行结果
function effect(fn, options = {}) { fn.options = options; // 新增 activeEffect = fn; // 执行副作用函数 fn(); } effect( () => { console.log(obj.foo); }, // options { // 调度器 scheduler 是一个函数 scheduler(fn) { // 修改参数,将副作用函数放在宏任务队列中执行 setTimeout(fn) }, } );
完整代码:
const data = { foo: 1 }; // 用一个全局变量存储被注册的副作用函数 let activeEffect; // 创建一个新桶来存储副作用函数,包含key和value const bucket = new WeakMap(); const obj = new Proxy(data, { get(target, key) { // target:当前对象,key:触发监听的key track(target, key); return target[key]; }, set(target, key, newVal) { target[key] = newVal; trigger(target, key); }, }); // track函数 function track(target, key) { // 没有正在执行的副作用函数 直接返回 if (!activeEffect) return target[key]; // 从这个桶中取出一个Map类型(key -> value) let depsMap = bucket.get(target); // 不存在,则创建一个Map与target关联 if (!depsMap) { bucket.set(target, (depsMap = new Map())); } // 根据key判断每个key上是否存在对应的副作用函数 let deps = depsMap.get(key); // 不存在,则新建一个new Set,并与key关联 if (!deps) { depsMap.set(key, (deps = new Set())); } // 最后将当前激活的副作用函数添加到桶中 deps.add(activeEffect); } // trigger函数 function trigger(target, key) { // 根据target从桶中取得depsMap,它是key --> effects const depsMap = bucket.get(target); if (!depsMap) return; // 根据key取得当前对应的副作用函数 const effects = depsMap.get(key); // 执行副作用函数 effects && effects.forEach((fn) => { if (fn.options.scheduler) { fn.options.scheduler(fn); } else { // 否则直接执行副作用函数(之前的默认行为) fn(); } }); } // effect 函数用于注册副作用函数 function effect(fn, options = {}) { fn.options = options; // 新增 activeEffect = fn; // 执行副作用函数 fn(); } effect( () => { console.log(obj.foo); }, // options { // 调度器 scheduler 是一个函数 scheduler(fn) { // 修改参数,将副作用函数放在宏任务队列中执行 setTimeout(fn); }, } ); obj.foo++; console.log("结束了");
除了控制副作用函数的执行顺序,通过调度器
还可以做到控制它的执行次数
- 正常的打印结果应该是
1,2,3
,但是我们只关心执行的最后结果,应该拿到的是1,3
,执行三次有些多余,这时,就需要使用到调度器
来修改执行次数
const data = { foo: 1 } const obj = new Proxy(data, { /* ... */ }) // 上文中的响应式 effect(() => { console.log(obj.foo) }) obj.foo++ obj.foo++;
- 实现不包含过渡阶段,使用调度器基于
promise
,可以直接修改当前当前调度函数
// 定义一个任务队列,使用它自动去重能力 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) => job()); }).finally(() => { // 结束后重置 isFlushing isFlushing = false; }); }
- 执行调度函数
effect( () => { console.log(obj.foo); }, // options { // 调度器 scheduler 是一个函数 scheduler(fn) { jobQueue.add(fn); // 调用 flushJob 刷新队列 flushJob(); }, } ); obj.foo++; obj.foo++;
到此这篇关于vue3调度器scheduler功能和用法详解的文章就介绍到这了,更多相关vue3调度器scheduler内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!