大家好我是小瑜,今天学习的是在Vue响应式中给effect函数添加 scheduler 也就是调度器,这样做的好处是可以给effect添加配置的方式,实现响应式的自定义设置执行顺序或者执行次数,可以使effect更加灵活。
要实现的目的是:
当trigger动作触发副作用函数重新执行时, 有能力决定副作用函数执行的时机,次数以及方式。
决定副作用函数的执行的时机
自定义修改执行顺序
例如下面这段代码,输出的结果是 1 2 结束了 要求将输出结果的顺序在不跳转现有代码的基础上进行输出的执行
const data = {foo:1}
const obj = new Proxy(data,{xxx})
effect(()=>{
console.log(obj.foo)
})
obj.foo ++
console.log('结束了')
// 输出:1 2 结束了
此时就需要在触发 effect 的时候添加一个 scheduler, 交给用户来控制执行顺序 可以个 effect 添加一项配置,提供给用户来手动操作
effect(
() => {
console.log("foo:", obj.foo);
},
{
// 调度器 scheduler 是一个函数
scheduler(fn) {
// 将副作用函数放到宏任务队列中执行
setTimeout(fn);
},
}
);
obj.foo++;
console.log("结束了");
在 effect中进行挂载保存 在trigger 中就需要将 scheduler 保存并执行
- 挂载
function effect(fn, options = {}) {
const effectFn = () => {
cleanup(effectFn);
activeEffect = effectFn;
effectStack.push(effectFn);
fn();
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
};
effectFn.deps = [];
// 将 options 挂载到 effectFn 上
effectFn.options = options; // 新增
effectFn();
}
- 执行
function trigger(target, key) {
const depsMap = bucket.get(target);
if (!depsMap) return;
const effects = depsMap.get(key);
const effectsToRun = new Set(effects);
effectsToRun.forEach((effectFn) => {
// 如果一个副作用函数存在调度器,则调用该调度器,并将副作用函数作为参数传递
if (effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn);
} else {
effectFn();
}
});
}
这里首先是将 下面这段配置传给 trigger, trigger中找到 scheduler 这个函数, 并判断是否存在,若存在就将此次effect作为 scheduler 的参数进行传递并执行, 如果不存在则执行 effect
{
// 调度器 scheduler 是一个函数
scheduler(fn) {
// 将副作用函数放到宏任务队列中执行
setTimeout(fn);
},
}
控制执行次数
例如下面这段代码的执行结果分别是:1 2 3 '结束了', 但是我想要关注的是执行最终的结果,并不关心执行过程, 我期望的输出结果为1 '结束了' 3。
effect(() => {
console.log(obj.foo);
});
obj.foo++;
obj.foo++;
console.log('结束了')
这里同样也可以借助 scheduler 来实现逻辑 首选是需要利用 Set 去重的机制 保存 非重复的 effect 这里就代表 **console.log(obj.foo) **有且只能有一个 并且来设置一个开关,来给Set添加effect 那么以上这段逻辑就都需要放在 scheduler 也就是 effect的第二项配置中自定义实现
/**
* 新增 scheduler 调度器
* 通过set 将任务添加到调度器任务队列中自动去重
*/
// 调度器任务队列
export const jobQueue = new Set();
// 使用 Promise.resolve() 创建一个 promise 实例,我们用它将一个任务添加到微任务队列
const p = Promise.resolve();
// 一个标志代表是否正在刷新队列
let isFlushing = false;
function flushJob() {
// 如果正在刷新则不执行
if (isFlushing) return;
isFlushing = true;
p.then(() => {
jobQueue.forEach((job) => job());
}).finally(() => {
isFlushing = false;
});
}
effect(
() => {
console.log(obj.foo);
},
{
scheduler(job) {
// 添加effect到微任务队列,并去重
jobQueue.add(job);
// 将微任务队列中的effect执行
flushJob();
},
}
);
obj.foo++;
obj.foo++;
console.log("结束了");
// 输出: 1 '结束了'
完整代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<style></style>
</head>
<body>
<script type="module">
import { effect, obj, jobQueue, flushJob } from "./01-index.js";
// 1.修改执行顺序
effect(
() => {
console.log("foo:", obj.foo);
},
{
// 调度器 scheduler 是一个函数
scheduler(fn) {
// 将副作用函数放到宏任务队列中执行
setTimeout(fn);
},
}
);
obj.foo++;
console.log("结束了");
// 2、控制执行顺序即执行顺序
effect(
() => {
console.log(obj.foo);
},
{
scheduler(job) {
jobQueue.add(job);
flushJob();
},
}
);
obj.foo++;
obj.foo++;
console.log("结束了");
// 此时的输出结果是 1 2 3 但是要求并不关心执行过程 只需要执行结果 1 3 '结束了'
</script>
</body>
</html>
const data = { foo: 1 };
let activeEffect;
const effectStack = [];
const bucket = new WeakMap();
export function effect(fn, options = {}) {
const effectFn = () => {
cleanup(effectFn);
activeEffect = effectFn;
effectStack.push(effectFn);
fn();
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
};
effectFn.deps = [];
// 将 options 挂载到 effectFn 上
effectFn.options = options; // 新增
effectFn();
}
export const obj = new Proxy(data, {
get(target, key) {
track(target, key);
return target[key];
},
set(target, key, newVal) {
target[key] = newVal;
trigger(target, key);
return true;
},
});
function track(target, key) {
if (!activeEffect) return target[key];
let depsMap = bucket.get(target);
if (!depsMap) {
bucket.set(target, (depsMap = new Map()));
}
let deps = depsMap.get(key);
if (!deps) {
depsMap.set(key, (deps = new Set()));
}
deps.add(activeEffect);
activeEffect.deps.push(deps);
}
function trigger(target, key) {
const depsMap = bucket.get(target);
if (!depsMap) return;
const effects = depsMap.get(key);
const effectsToRun = new Set(effects);
effectsToRun.forEach((effectFn) => {
// 如果一个副作用函数存在调度器,则调用该调度器,并将副作用函数作为参数传递
if (effectFn.options.scheduler) {
console.log(effectFn.options);
effectFn.options.scheduler(effectFn);
} else {
effectFn();
}
});
}
function cleanup(fn) {
fn && fn.deps.forEach((dep) => dep.delete(fn));
fn.deps.length = 0;
}
/**
* 新增 scheduler 调度器
* 通过set 将任务添加到调度器任务队列中自动去重
*/
// 调度器任务队列
export const jobQueue = new Set();
const p = Promise.resolve();
let isFlushing = false;
export function flushJob() {
if (isFlushing) return;
isFlushing = true;
p.then(() => {
jobQueue.forEach((job) => job());
}).finally(() => {
isFlushing = false;
});
}