4.7 调度执行
给
effect增加options.scheduler,把副作用函数「何时执行、执行几次」的控制权交给用户。
一、什么是可调度
可调度指当 trigger 触发副作用函数重新执行时,有能力决定副作用函数执行的时机、次数以及方式。
effect(
() => console.log(obj.foo),
{
// 调度器是一个函数,接收副作用函数作为参数
scheduler(fn) {
// 把副作用函数放到宏任务队列中执行
setTimeout(fn)
}
}
)
obj.foo++ // 交给调度器 → 稍后执行
console.log('结束了')
// 输出顺序:1 → 结束了 → 2
不加调度器时输出是 1 → 2 → 结束了;加上之后执行顺序被改写成先打印「结束了」。
二、实现:effect 接收 options
function effect(fn, options = {}) {
const effectFn = () => {
cleanup(effectFn)
activeEffect = effectFn
effectStack.push(effectFn)
fn()
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
// 把 options 挂到 effectFn 上,供 trigger 读取
effectFn.options = options
effectFn.deps = []
effectFn()
return effectFn
}
trigger 派发时改为「有调度器就交给调度器」:
effectsToRun.forEach(effectFn => {
if (effectFn.options.scheduler) {
// 存在调度器:执行时机交给用户决定
effectFn.options.scheduler(effectFn)
} else {
effectFn() // 默认行为:直接执行
}
})
三、控制执行次数:jobQueue + flushJob
obj.foo++ // 1
obj.foo++ // 2
obj.foo++ // 3
// 不做处理会打印 4 次(初始 1 次 + 变更 3 次)
// 任务队列
const jobQueue = new Set()
// 用 Promise.resolve() 拿到一个 promise,用来把任务丢进微任务队列
const p = Promise.resolve()
// 标志:是否正在刷新队列
let isFlushing = false
function flushJob() {
if (isFlushing) return // 正在刷新则什么都不做
isFlushing = true
// 在微任务队列中刷新 jobQueue(微任务晚于当前同步代码)
p.then(() => {
jobQueue.forEach(job => job())
}).finally(() => {
isFlushing = false // 刷新结束后重置
})
}
effect(
() => console.log(obj.foo),
{
scheduler(fn) {
jobQueue.add(fn) // 入队(Set 自动去重)
flushJob() // 尝试刷新队列
}
}
)
Set的去重能力让jobQueue里最终只有一项(同一个副作用函数);- 三次自增会同步且连续调用
flushJob三次,但isFlushing标志保证一个事件循环内只刷新一次,副作用函数也只执行一次 → 输出1 → 4。
| 概念 | 作用 |
|---|---|
options.scheduler | 决定副作用函数何时、以何种方式执行 |
jobQueue(Set) | 收集待执行任务,自动去重 |
Promise.resolve().then | 把刷新动作放进微任务队列 |
isFlushing | 保证同一轮事件循环只刷新一次 |
四、易错点
scheduler收到的是副作用函数本身,用户必须自己决定何时调用它,否则副作用函数永远不会执行。isFlushing必须在finally中重置,否则队列刷新一次之后就再也不会被刷新。
相关
- 4.6 避免无限递归循环:
trigger中的守卫条件 - 4.8 计算属性 computed 与 lazy:
lazy与scheduler组合出计算属性