watch和watchEffect
# 概述
watch
和watchEffect
是 Vue3 中常用的两个 api,用于监听数据变化。除此之外还有 vue3 还提供了watchPostEffect
和watchSyncEffect
# watch
watch(source, cb, options?)
watch
主动监听,在初始化时会执行一次回调函数,当监听的数据source
发生变化时会执行回调函数cb
。cb
接收两个参数,一个是newValue
,一个是oldValue
。watch
的source
可以是一个getter
函数,也可以是一个ref
对象,也可以是一个reactive
对象。可以同时监听多个数据,也存在一定的性能的开销。
# watchEffect
watchEffect(effect, options?)
watchEffect
被动监听,在初始化时会执行一次回调函数,当回调函数内部中的任何响应式数据发生变化都会执行回调函数effect
,它也无需指定具体的依赖。effect
接收一个参数,是onInvalidate
,用于注册一个清理函数,当监听的数据发生变化时,会执行清理函数。
# watchPostEffect
watchPostEffect(effect)
: 和watchEffect
类似,自动监听,回调函数会在 DOM 更新之后调用,能够确保回调函数执行时可以获取到更新后的 DOM,适用于需要在 DOM 更新后执行操作的情况,如读取元素的位置、尺寸等信息
# watchSyncEffect
watchSyncEffect(effect)
: 同步监听,每当依赖变化时立即触发回调函数,而不会等待下一次事件循环。适用于需要立即响应数据变化的情况,如实时计算或者紧急数据更新等
# 四种监听器的实现
vue3 内部实现这 4 种监听器的原理大同小异,都是返回调用doWatch
函数,位于packages\runtime-core\src\apiWatch.ts
# doWatch
原理
# 传参
doWatch
函数接收三个参数,分别是source
、cb
和options
。
watch
可以传入三个参数,options={immediate,deep,flush,once}
watchEffect
传入两个参数,options={immediate,deep,flush,once}
- 对于
watchPostEffect
和watchSyncEffect
来说,只有source
参数作为回调函数,watchPostEffect
函数内部options
是{flush:post}
;watchSyncEffect
函数内部options
是{flush:sync}
# 函数内部逻辑
首先doWatch
会判断是否cb
和once
,如果有,就将其包装成一个新的函数,内部调用unwatch
用于取消监听。
if (cb && once) {
const _cb = cb;
cb = (...args) => {
_cb(...args);
unwatch();
};
}
2
3
4
5
6
7
紧接着就是判断source
的类型,从而设置不同的getter
函数,getter
函数用于后面定义effect
。同时还会判断是否是多源,设置isMultiSource
的值,如果source
是数组,则isMultiSource
为true
。如果是watch
,且options
种的deep
为true
,则为深度监听。
后面doWatch
定义了onClenup
函数,用于注册一个清理函数,当监听的数据发生变化时,会执行清理函数。
doWatch
内部还针对 SSR 做了单独处理,定义不同的清理函数。
doWatch
定义了一个SchedulerJob
类的 job 函数,根据flush
的值,定义不同的scheduler
。由此可知只有watch
和watchEffect
的job
会是会进入队列,调用queueJob
。
let scheduler: EffectScheduler
if (flush === 'sync') {
scheduler = job as any // the scheduler function gets called directly
} else if (flush === 'post') {
scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
job.pre = true
if (instance) job.id = instance.uid
scheduler = () => queueJob(job)
}
2
3
4
5
6
7
8
9
10
至此,我们可以拿到getter
和scheduler
,通过const effect = new reactivity.ReactiveEffect(getter, shared.NOOP, scheduler)
定义一个effect
, effect
是响应式里提到的一个概念,它会自动追踪其依赖,并在依赖变更时重新运行自身。
然后,定义了unwatch
函数作为doWatch
的返回值,用于取消监听。其内部会调用effect.stop()
,停止effect
的运行。
最后一部分就是doWatch
的初始化运行
watch
:当cb
存在,若immediate
为true
,则直接运行job
,否则调用effect.run()
赋值给oldValue
watchPostEffect
:会运行一次queuePostRenderEffect
,将job
放入队列中watchEffet
则和watchSyncEffect
一样,直接运行effect.run()