watch

主要用来监听响应式数据(被ref或reactive包裹的数据)的变化。

1
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
<template>
<div>
case1: <input v-model="message" type="text">
<hr>
case2: <input v-model="message2" type="text">
<hr>
case3: <input v-model="message3.foo.bar.baz.value" type="text">
</div>
</template>

<script setup lang="ts">
import { ref, reactive, watch } from "vue"

const message = ref<string>("野兽先辈")

const message2 = ref<string>("UDK姐贵")

const message3 = reactive({
foo: {
bar: {
baz: {
value: "qux",
tag: "qur"
}
}
}
})


// 参数1:要被侦听的数据 参数2:回调函数cb
// watch(message, (newValue, oldValue)=> {
// // 参数1:新值 参数2:旧值
// console.log(newValue, oldValue)
// })

// 也可以侦听多个词
// 此时参数1和回调函数的两个参数都是数组,它们的顺序对应
watch([message, message2], (newValue, oldValue)=> {
console.log(newValue, oldValue)
})

// 参数3是options配置项
watch(message3, (newValue, oldValue)=> {
// 监听引用类型时,所返回的新值和旧值是一样的
console.log(newValue, oldValue)
}, {
// 开启深度监听,能监听到对象的深层嵌套内容
// 如果引用类型是由reactive包裹的,则不开启深度监听也能被监听
deep: true,

// 立马就让cb执行一次,此次的newValue为数据的默认值,oldValue为undefined
immediate: true,

// pre:组件更新之前执行 sync:与组件更新同步执行 post:组件更新之后执行
flush: "pre"
})

// 这样监听一个对象的某一属性值
// watch(()=> message3.foo.bar.baz.value, (newValue, oldValue)=> {
// console.log(newValue, oldValue)
// })
</script>

watchEffect

另一种监听数据的方式

1
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
<template>
<div>
case1: <input v-model="message" type="text" ref="ipt">
<hr>
case2: <input v-model="message2" type="text">
<hr>
<button @click="stopWatch">停止监听</button>
</div>
</template>

<script setup lang="ts">
import { ref, watchEffect } from "vue"

const message = ref<string>("野兽先辈")

const message2 = ref<string>("野兽妹")

const ipt = ref<HTMLInputElement>()

// 只要是写进回调函数内的数据都会被监听
// 一进页面,回调函数就会立即被调用
// 返回值是一个监听停止操作函数
const stop = watchEffect((oninvalidate)=> {
console.log("message=====>", message.value)
console.log("message2=====>", message2.value)

// 刚开始dom还没挂载,所以输出的一定是undefined
// 配置项中使用flush: "post"即可挂载后读
console.log(ipt.value, "input")

// 在监听cb之前做一些其他处理,这个函数的调用先于函数体内其他代码
// 可以用来进行防抖、清除接口等操作
oninvalidate(()=> {
console.log("before")
})
}, {
// DOM挂载后读
flush: "post",

// 开发环境的调试函数
onTrigger(e) {
console.log(e)
}
})

// 调用监听停止操作函数就能停止该数据监听
const stopWatch = ()=> stop()
</script>

源码

在Vue源码(/package/runtime-core/src/apiWatch.ts)中可以看到watch的源码。

1
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272

export function watch<T = any, Immediate extends Readonly<boolean> = false>(
source: T | WatchSource<T>,
cb: any,
options?: WatchOptions<Immediate>
): WatchStopHandle {
if (__DEV__ && !isFunction(cb)) {
warn(
`\`watch(fn, options?)\` signature has been moved to a separate API. ` +
`Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
`supports \`watch(source, cb, options?) signature.`
)
}
return doWatch(source as any, cb, options)
}

function doWatch(
source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null,
{ immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): WatchStopHandle {
// 一些错误处理
if (__DEV__ && !cb) {
if (immediate !== undefined) {
warn(
`watch() "immediate" option is only respected when using the ` +
`watch(source, callback, options?) signature.`
)
}
if (deep !== undefined) {
warn(
`watch() "deep" option is only respected when using the ` +
`watch(source, callback, options?) signature.`
)
}
}

const warnInvalidSource = (s: unknown) => {
warn(
`Invalid watch source: `,
s,
`A watch source can only be a getter/effect function, a ref, ` +
`a reactive object, or an array of these types.`
)
}

const instance =
getCurrentScope() === currentInstance?.scope ? currentInstance : null
// const instance = currentInstance
let getter: () => any
let forceTrigger = false
let isMultiSource = false

// 格式化source,赋给getter函数
if (isRef(source)) {
// 如果是ref对象,创建一个getter函数并读取了ref对象的value属性
getter = () => source.value
forceTrigger = isShallow(source)
} else if (isReactive(source)) {
// 如果是reactive对象,直接返回一个getter函数,并设deep为true
getter = () => source
deep = true
} else if (isArray(source)) {
isMultiSource = true
forceTrigger = source.some(s => isReactive(s) || isShallow(s))
getter = () =>
// 如果是数组,遍历并处理里面的ref和reactive
source.map(s => {
if (isRef(s)) {
return s.value
} else if (isReactive(s)) {
// 递归对每个属性进行侦听
return traverse(s)
} else if (isFunction(s)) {
// 进行某种封装
return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
} else {
__DEV__ && warnInvalidSource(s)
}
})
} else if (isFunction(source)) {
// 如果是函数,判断cb是否存在
if (cb) {
// getter with cb
// cb存在,getter对source进行简单的封装
getter = () =>
callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
} else {
// no cb -> simple effect
// cb不存在,会执行watch effect
getter = () => {
if (instance && instance.isUnmounted) {
return
}
if (cleanup) {
cleanup()
}
return callWithAsyncErrorHandling(
source,
instance,
ErrorCodes.WATCH_CALLBACK,
[onCleanup]
)
}
}
} else {
getter = NOOP
__DEV__ && warnInvalidSource(source)
}

// 2.x array mutation watch compat
if (__COMPAT__ && cb && !deep) {
const baseGetter = getter
getter = () => {
const val = baseGetter()
if (
isArray(val) &&
checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)
) {
traverse(val)
}
return val
}
}

// 处理deep深度监听
if (cb && deep) {
const baseGetter = getter
// 递归对每个属性进行侦听,比较耗时,非必要不开启
getter = () => traverse(baseGetter())
}

let cleanup: () => void
let onCleanup: OnCleanup = (fn: () => void) => {
cleanup = effect.onStop = () => {
callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
}
}

// in SSR there is no need to setup an actual effect, and it should be noop
// unless it's eager or sync flush
let ssrCleanup: (() => void)[] | undefined
if (__SSR__ && isInSSRComponentSetup) {
// we will also not call the invalidate callback (+ runner is not set up)
onCleanup = NOOP
if (!cb) {
getter()
} else if (immediate) {
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
getter(),
isMultiSource ? [] : undefined,
onCleanup
])
}
if (flush === 'sync') {
const ctx = useSSRContext() as SSRContext
ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = [])
} else {
return NOOP
}
}

// 给旧值作初始化
let oldValue: any = isMultiSource
? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
: INITIAL_WATCHER_VALUE

// 进行监听的核心,cb也在这里调用
const job: SchedulerJob = () => {
if (!effect.active) {
return
}
if (cb) {
// watch(source, cb)
// 获取新值
const newValue = effect.run()
if (
deep ||
forceTrigger ||
(isMultiSource
? (newValue as any[]).some((v, i) =>
hasChanged(v, (oldValue as any[])[i])
)
: hasChanged(newValue, oldValue)) ||
(__COMPAT__ &&
isArray(newValue) &&
isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance))
) {
// cleanup before running cb again
if (cleanup) {
cleanup()
}
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
newValue,
// pass undefined as the old value when it's changed for the first time
// 第一次(immediate)执行旧值 oldValue是undefined或空数组
oldValue === INITIAL_WATCHER_VALUE
? undefined
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
? []
: oldValue,
onCleanup
])
// 直接赋值,更新旧值,一切操作执行完才会更新
// 如果是对象的话,就直接引用了,所以会导致新旧值一样
oldValue = newValue
}
} else {
// 变成watchEffect模式
effect.run()
}
}

// important: mark the job as a watcher callback so that scheduler knows
// it is allowed to self-trigger (#1727)
job.allowRecurse = !!cb

// 调度模式
let scheduler: EffectScheduler
if (flush === 'sync') {
// 同步执行
scheduler = job as any // the scheduler function gets called directly
} else if (flush === 'post') {
// 把job传给queuePostRenderEffect(组件更新后执行)
scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
// 组件更新前执行
// default: 'pre'
job.pre = true
if (instance) job.id = instance.uid
scheduler = () => queueJob(job)
}

// 收集依赖(没有更新,所以job还不会走)
// 更新的时候就会调用scheduler,进而调用job,这时就能读到新值了
const effect = new ReactiveEffect(getter, scheduler)

if (__DEV__) {
effect.onTrack = onTrack
effect.onTrigger = onTrigger
}

// initial run
if (cb) {
// 传了cb
if (immediate) {
// 设置了immediate,立马调用一次
job()
} else {
// 没有设置immediate,给旧值设置初始值
oldValue = effect.run()
}
} else if (flush === 'post') {
queuePostRenderEffect(
effect.run.bind(effect),
instance && instance.suspense
)
} else {
effect.run()
}

// 取消侦听的函数作为返回值
const unwatch = () => {
effect.stop()
if (instance && instance.scope) {
remove(instance.scope.effects!, effect)
}
}

if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
return unwatch
}