在响应式地跟踪其依赖项时立即运行一个函数,并在更改依赖项时重新运行它。
import {
ref, watchEffect } from 'vue'
export default {
setup() {
const count = ref(0)
setInterval(() => {
count.value++
}, 1000)
/** 这个hook用以监听count.value的变化 */
watchEffect(() => console.log(count.value))
// -> logs 0
// -> logs 1
// -> logs 2
// -> logs 3
// -> logs 4
// -> logs ...
return {
count }
}
}
watchEffect里面所有用到的值,其中任何一个发生变化,就会触发watchEffect第一个参数(函数体的执行)
import {
ref, watchEffect } from 'vue'
export default {
setup() {
const count = ref(0)
const dataCount = ref(0)
setInterval(() => {
count.value++
dataCount.value++
}, 1000)
/** 这个hook用以监听count.value的变化 */
watchEffect(() => console.log(count.value, dataCount.value))
// -> logs 0 0
// -> logs 1 1
// -> logs 2 2
// -> logs 3 3
// -> logs 4 4
// -> logs ...
return {
count, dataCount }
}
}
停止侦听,消除副作用 watchEffect返回的值就是停止侦听的函数。
const stop = watchEffect(() => console.log(count.value))
// setup中,在setup的onUnmounted生命周期停止侦听
onUnmounted(() => {
stop()
})
wtachEffect的类型
function watchEffect(
effect: (onInvalidate: InvalidateCbRegistrator) => void,
options?: WatchEffectOptions
): StopHandle
/** 由此可见 WatchEffectOptions 还有可配置选项 */
interface WatchEffectOptions {
flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
}
interface DebuggerEvent {
effect: ReactiveEffect
target: any
type: OperationTypes
key: string | symbol | undefined
}
type InvalidateCbRegistrator = (invalidate: () => void) => void
/** 这是watchEffect返回的stop函数,用于消除监听 */
type StopHandle = () => void
import {
ref, watchEffect, watch, onUnmounted } from 'vue'
export default {
setup() {
const count = ref(0)
const dataCount = ref(0)
setInterval(() => {
count.value++
}, 1000)
// 停止侦听, watchEffect返回的值就是停止侦听的函数。
watchEffect(() => console.log(count.value), {
// 将在依赖项变更导致副作用被触发时被调用。
onTrigger() {
// debugger
console.log('onTrigger触发了')
},
// 将在响应式 property 或 ref 作为依赖项被追踪时被调用。
onTrack() {
console.log('onTrack触发了')
},
// post 在组件更新后触发,这样你就可以访问更新的 DOM。 注意:这也将推迟副作用的初始运行,直到组件的首次渲染完成。
// 默认 pre
// flush 选项还接受 sync,这将强制效果始终同步触发。然而,这是低效的,应该很少需要。
flush: 'pre'
// onTrack 和 onTrigger 只能在开发模式下工作。
})
return {
count }
}
清除副作用
有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (即完成之前状态已改变了) 。所以侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:
副作用即将重新执行时
侦听器被停止 (如果在 setup() 或生命周期钩子函数中使用了 watchEffect,则在组件卸载时)
watchEffect(onInvalidate => {
const token = performAsyncOperation(id.value)
onInvalidate(() => {
// id has changed or watcher is stopped.
// invalidate previously pending async operation
token.cancel()
})
})
在执行数据请求时,副作用函数往往是一个异步函数:
const data = ref(null)
watchEffect(async onInvalidate => {
onInvalidate(() => {
/* ... */ }) // 我们在Promise解析之前注册清除函数
data.value = await fetchData(props.id)
})
总结watchEffect因为依赖变化而重新触发时,上一次watchEffect触发时产生的数据可能还在,或者上一次watchEffect触发的函数可能还在执行,而上一次的数据或者函数,对于业务本身是没有意义的,因为我们肯定是要获取最新的一个状态,执行最新的结果,或者保存最新的数据。
watch API 与选项式 API this.$watch (以及相应的 watch 选项) 完全等效。watch 需要侦听特定的数据源,并在单独的回调函数中执行副作用。默认情况下,它也是惰性的——即回调仅在侦听源发生更改时被调用。
侦听一个源
第一个参数可以是一个具体的变量,也可以是一个函数返回一个变量。
// 侦听一个 getter
const state = reactive({
count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
// 直接侦听一个 ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
侦听多个源
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
watch的类型
// 侦听单一源
function watch<T>(
source: WatcherSource<T>,
callback: (
value: T,
oldValue: T,
onInvalidate: InvalidateCbRegistrator
) => void,
options?: WatchOptions
): StopHandle
// 侦听多个源
function watch<T extends WatcherSource<unknown>[]>(
sources: T
callback: (
values: MapSources<T>,
oldValues: MapSources<T>,
onInvalidate: InvalidateCbRegistrator
) => void,
options? : WatchOptions
): StopHandle
type WatcherSource<T> = Ref<T> | (() => T)
type MapSources<T> = {
[K in keyof T]: T[K] extends WatcherSource<infer V> ? V : never
}
// 参见 `watchEffect` 类型声明共享选项,由此可见vue3.0的watch可配置项与vue2.0的watch可配置项完全一致
interface WatchOptions extends WatchEffectOptions {
immediate?: boolean // 默认:false
deep?: boolean
}
尝试检查深度嵌套对象或数组中的 property 变化时,仍然需要 deep 选项设置为 true。
const state = reactive({
id: 1,
attributes: {
name: "",
},
});
watch(
() => state,
(state, prevState) => {
console.log(
"not deep ",
state.attributes.name,
prevState.attributes.name
);
}
);
watch(
() => state,
(state, prevState) => {
console.log(
"deep ",
state.attributes.name,
prevState.attributes.name
);
},
{
deep: true }
);