watch和watcherEffect源码解析
# 概览
在《响应式中的watch实现》中讲解过watch
的源码实现,本文主要介绍在runtime-core
篇中doWatch
的实现,以及watch
和watchEffect
的区别。具体实现是在packages\runtime-core\src\apiWatch.ts
.
# 源码解析
# doWatch
doWatch
就是基于reactivity模块中的watch
实现的,对参数options
进行了定制包装,并且支持在服务端渲染中使用。主要是对scheduler
、augmentJob
和call
进行了包装。
doWatch
的源码实现如下:
function doWatch(source, cb, options = {}) {
// immediate:是否立即执行回调,deep:是否深度监听 flush:调度时机 once:是否只触发一次
const { immediate, deep, flush, once } = options;
// 定义基础配置项
const baseWatchOptions = shared.extend({}, options);
// 判断是否立即执行。若存在回调函数cb且immediate为true,或者没有回调函数cb且flush值不为post,则也是会立即执行
const runsImmediately = cb && immediate || !cb && flush !== 'post';
// SSR 处理
let ssrCleanup;
// 若是SSR环境,则isInSSRComponentSetup为true
if (isInSSRComponentSetup) {
if (flush === 'sync') {
// 若flush为sync,则调用useSSRContext获取SSR上下文,并且定义上下文的_watcherHandlers属性
const ctx = useSSRContext();
ssrCleanup = ctx._watcherHandles || (ctx._watcherHandles = []);
} else if (!runsImmediately) {
// 若flush不是sync且不需要立即运行,则返回一个空操作的停止句柄,即不会允许监听
const watchStopHandle = () => { };
watchStopHandle.stop = shared.NOOP;
watchStopHandle.resume = shared.NOOP;
watchStopHandle.pause = shared.NOOP;
return watchStopHandle;
}
}
// 获取当前组件实例
const instance = currentInstance;
// 定义基础配置项的call方法,保证回调会在`callWithAsyncErrorHandling`中执行,可以捕获异步错误
baseWatchOptions.call = (fn, type, args) => callWithAsyncErrorHandling(fn, instance, type, args);
// 定义isPre变量,若flush不是post也不是sync,则它的值为true,表示是一个前置观察者
let isPre = false;
if (flush === 'post') {
// 若flush是post,则使用queuePostRenderEffect将回调放入后置队列中,在DOM更新后执行
baseWatchOptions.scheduler = (job) => {
queuePostRenderEffect(job, instance && instance.suspense);
}
} else if (flush !== 'sync') {
// 若flush不是post也不是sync,这也是使用watchEffect的默认情况,则调用queueJob将job放入队列中,在组件更新前执行,
isPre = true;
baseWatchOptions.scheduler = (job, isFirstRun) => {
// 第一次运行job时,isFirstRun为true,会立即运行job;后续job会放入队列中运行,即watchEffect(cb);cb第一次会直接运行,后续cb中的响应式数据发生变化时,cb是在队列中运行的
if (isFirstRun) {
job();
} else {
queueJob(job);
}
}
}
// 定义基础配置项的augmentJob方法
baseWatchOptions.augmentJob = (job) => {
if (cb) {
// 若存在回调,则标记job的flags的第2位为1
job.flags |= 4;
}
if (isPre) {
// 若flush不为post,也不是flush,则表示这是一个前置观察者,标记job的flags的第1位为1
job.flags |= 2;
// 若存在实例,则关联组件的实例
if (instance) {
job.id = instance.id;
job.i = instance;
}
}
}
// 定义watchHandle,实际上就是执行reactivity中的watch方法,返回的就是清理方法
const watchHandle = reactivity.watch(source, cb, baseWatchOptions);
if (isInSSRComponentSetup) {
// 在SSR环境中,若存在清理函数,则将watchHandle放入ssrCleanup中
if (ssrCleanup) {
ssrCleanup.push(watchHandle)
} else if (runsImmediately) {
// 若是立即执行,则立即调用清理函数
watchHandle();
}
}
// 最后返回清理方法watchHandle
return watchHandle;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# 监听方法
基于doWatch
方法,vue3定义了四种监听方法:watch
、watchEffect
、watchPostEffect
和watchSyncEffect
。watchPostEffect
和watchSyncEffect
就是特殊的watchEffect
,指定了flush
的值。
# watch
watch
方法就是调用doWatch
并返回句柄,source
为主动监听的对象,可以是ref
/reactive
响应式对象或者由它们组成的数组或者是一个函数,函数返回的结果是它们。
function watch(source, cb, options) {
return doWatch(source, cb, options)
}
2
3
# watchEffect
watchEffect
是被动监听,参数effect
是一个回调函数,里面会包含对响应式数据的读取。effect
会主动触发一次,后续会在队列中执行。只要effect
中的响应式数据发生变化,effect
就会被触发执行。默认情况下,即options
不传参,watchEffect
是一个前置监听,effect
会在DOM 更新前触发。
watchEffect
源码实现如下:
function watchEffect(effect, options) {
return doWatch(effect, null, options)
}
2
3
watchEffect
与watch
相比,对于doWatch
而言,watchEffect
就是对effect
进行监听,而没有回调函数cb
。
# watchPostEffect
watchPostEffect
就是指定了{flush:'post'}
,在调度器scheduler
的处理中,effect
会通过queuePostRenderEffect
将job
放入后置队列中等待执行,即effect
会在DOM更新后再执行。
需要注意的是watchPostEffect
中的effect
没有首次主动执行。
watchPostEffect
源码如下:
function watchPostEffect(effect, options) {
return doWatch(effect, null, { flush: "post" })
}
2
3
# watchSyncEffect
watchSyncEffect
是指定了{flush:'sync'}
,表示effect
是同步执行,它没有调度器,不会放入队列中去执行,但是会在首次主动执行一次effect
。
watchSyncEffect
源码如下:
function watchSyncEffect(effect, options) {
return doWatch(effect, null, { flush: "sync" })
}
2
3