4.10 立即执行的 watch 与回调执行时机
抽出
job函数后,watch既能支持immediate立即执行一次,也能通过flush指定回调的执行时机。
一、抽出 job 函数
把 scheduler 里的逻辑提成独立的 job,就能在「初始化」与「变更」两个时机复用:
let oldValue, newValue
const job = () => {
newValue = effectFn() // 重新执行副作用函数拿新值
cb(newValue, oldValue) // 执行回调
oldValue = newValue // 更新旧值
}
const effectFn = effect(
() => getter(),
{
lazy: true,
scheduler: job // 变更时执行 job
}
)
二、immediate:创建时立即执行一次
if (options.immediate) {
job() // 立即执行 → 触发回调
} else {
oldValue = effectFn() // 只拿旧值,不执行回调
}
watch(
() => obj.foo,
(newValue, oldValue) => console.log(newValue, oldValue),
{ immediate: true }
)
immediate: true时回调会在watch创建时立刻执行一次;此时没有旧值,所以第一次回调的oldValue是undefined。
三、flush:指定回调的执行时机
flush 本质上是在指定调度函数的执行时机:
const effectFn = effect(
() => getter(),
{
lazy: true,
scheduler: () => {
if (options.flush === 'post') {
// 'post':把 job 放到微任务队列,等同步代码跑完再执行
const p = Promise.resolve()
p.then(job)
} else {
// 默认分支相当于 'sync':同步执行
job()
}
}
}
)
flush 取值 | 执行时机 | 本章能否模拟 |
|---|---|---|
'sync' | 同步执行 job | ✅(默认分支) |
'post' | 微任务队列中执行,晚于当前同步代码 | ✅ |
'pre' | 组件更新前执行 | ❌ 涉及组件更新时机,本章无法模拟 |
watch(() => obj.foo, cb, { immediate: true, flush: 'post' })
obj.foo++
console.log('这个 log 比回调函数先执行') // 回调被推迟到微任务,所以先打印这句
obj.foo++
四、易错点
immediate为真时走job(),为假时才用oldValue = effectFn()取初值;两者不能同时做,否则回调会被执行两次。flush: 'pre'的语义是「组件更新前」,需要渲染器配合,入门实现里只能先留空。
相关
- 4.9 watch 的实现原理:
watch的完整演进过程 - 4.11 过期的副作用:异步回调带来的竞态问题