watch
监听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。
默认是懒监听的,即仅在侦听源发生变化时才执行回调函数。
第一个参数是监听源
这个来源可以是以下几种:
- 一个函数,返回一个值
- 一个 ref
- 一个响应式对象
- ...或是由以上类型的值组成的数组
第二个参数是在发生变化时要调用的回调函数
这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。
当监听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。
第三个可选的参数是一个对象
支持以下这些选项:
immediate:在监听器创建时立即触发回调。第一次调用时旧值是undefined。deep: 是否开启深度监听。flush:调整回调函数的刷新时机。onTrack / onTrigger:调试监听器的依赖。
flush: 'pre' // 组件更新前的调用
flush: 'post' // 组件更新后的调用
flush: 'sync' // 强制效果始终同步触发(如果有多个属性同时更新,这将导致一些性能和数据一致性的问题)
具体使用
监听一个 getter 函数
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
console.log('新值', count);
console.log('旧值', prevCount);
}
)
监听一个 ref
const count = ref(0)
watch(count, (count, prevCount) => {
console.log('新值', count);
console.log('旧值', prevCount);
})
监听一个 reactive
当直接侦听一个响应式对象时,侦听器会自动启用深层模式,所以使用reactive监听深层对象,开启和不开启deep效果一样
import { ref, watch ,reactive} from 'vue'
let message = reactive({
nav:{
bar:{
name:""
}
}
})
watch(
message,
(newVal, oldVal) => {
console.log('新值', newVal);
console.log('旧值', oldVal);
},
{ deep: true }
)
监听多个值
import { ref, watch ,reactive} from 'vue'
let msg1 = ref('msg1')
let msg2 = ref('msg2')
watch([msg1, msg2], (newVal, oldVal) => {
console.log('新值', newVal); // [msg1, msg2]
console.log('旧值', oldVal); // [prevMsg1, prevMsg2]
})
watchEffect
立即执行传入的函数,同时会响应式追踪其函数内的依赖,并在其依赖变更时重新运行该函数。
第一个参数就是要运行的函数
这个副作用函数的参数也是一个函数,用来注册清理回调。清理回调会在该副作用下一次执行前被调用,可以用来清理无效的副作用,例如等待中的异步请求 (参见下面的示例)。
let msg1 = ref<string>('msg1')
let msg2 = ref<string>('msg2')
watchEffect(() => {
console.log('msg1', msg1.value);
console.log('msg2', msg2.value);
})
第二个参数是一个可选的选项
可以用来调整副作用的刷新时机或调试副作用的依赖。
支持以下这些选项:
flush:调整回调函数的刷新时机。onTrack / onTrigger:调试监听器的依赖。
flush: 'pre' // 组件更新前的调用
flush: 'post' // 组件更新后的调用
flush: 'sync' // 强制效果始终同步触发(如果有多个属性同时更新,这将导致一些性能和数据一致性的问题)
清除副作用
在触发监听之前会调用一个函数 可以用于处理你的逻辑
import { watchEffect, ref } from 'vue'
watchEffect((oninvalidate) => {
oninvalidate(()=>{
// 在触发监听之前会先执行这里的逻辑
})
})
返回值一个用来停止更新的函数
let msg1 = ref<string>('msg1')
let msg2 = ref<string>('msg2')
const stop = watchEffect((oninvalidate) => {
console.log('msg1', msg1.value);
console.log('msg2', msg2.value);
oninvalidate(()=>{
})
},{
flush:"post",
onTrigger () {
}
})
stop() // 调用后不再监听
watch 与 watchEffect 源码分析
源码地址: /packages/runtime-core/apiWatch.ts
// 通过函数重载支持多种传参形式
export function watch<
T extends object,
Immediate extends Readonly<boolean> = false
>(
source: T,
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
options?: WatchOptions<Immediate>
): WatchStopHandle
// implementation
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.`
)
}
// 将参数传递给doWatch去做相应的监听
return doWatch(source as any, cb, options)
}
doWatch
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......
}
if (deep !== undefined) {
// warn......
}
}
const warnInvalidSource = (s: unknown) => {
// warn......
}
const instance =
getCurrentScope() === currentInstance?.scope ? currentInstance : null
// const instance = currentInstance
let getter: () => any
let forceTrigger = false // 是否深层次遍历
let isMultiSource = false
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)) { // 如果传递是数组 就遍历数组,处理数组内的ref和reactive
isMultiSource = true
forceTrigger = source.some(s => isReactive(s) || isShallow(s))
getter = () =>
source.map(s => {
if (isRef(s)) { // 是ref对象
return s.value // 直接返回.value
} else if (isReactive(s)) { // 是一个reactive对象
return traverse(s) // 实现了一个递归去遍历reactive对象的属性
} else if (isFunction(s)) {
return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
} else { // 报错,只能监听响应式对象
__DEV__ && warnInvalidSource(s)
}
})
} else if (isFunction(source)) { // 如果传递是一个函数,判断有没有传入第二个参数
if (cb) {
// getter with cb
getter = () =>
callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
} else {
// no cb -> simple effect
// 没有传入回调 相当于watchEffect
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
}
}
// 如果有传入第二个参数且深度监听
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)
}
}
// 初始化旧值
let oldValue: any = isMultiSource
? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
: INITIAL_WATCHER_VALUE
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
// 第一次执行时 旧值就是undefind或者空数组
oldValue === INITIAL_WATCHER_VALUE
? undefined
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
? []
: oldValue,
onCleanup
])
oldValue = newValue
}
} else {
effect.run()
}
}
// important: mark the job as a watcher callback so that scheduler knows
// it is allowed to self-trigger (#1727)
job.allowRecurse = !!cb
flush
// 使用调度器
let scheduler: EffectScheduler
if (flush === 'sync') { // 同步执行
scheduler = job as any // the scheduler function gets called directly
} else if (flush === 'post') {
// queuePostRenderEffect 组件更新后执行
scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
// default: 'pre'
job.pre = true
if (instance) job.id = instance.uid
scheduler = () => queueJob(job)
}
// 收集了一下依赖 但没有触发依赖更新
const effect = new ReactiveEffect(getter, scheduler)
// 有设置onTrack跟onTrigger调试的话
if (__DEV__) {
effect.onTrack = onTrack
effect.onTrigger = onTrigger
}
}
更新
// initial run
if (cb) {
if (immediate) {
job() // 如果设置了immediate 立即执行 就立马调用job
} else {
oldValue = effect.run() // 把oldValue设置成当前值
}
} 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)
}
}