计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。
1.侦听数据源类型
watch 的第一个参数可以是不同形式的“数据源”:它可以是
- 一个 ref (包括计算属性)、 一个响应式对象
- 一个 getter 函数、
- 或多个数据源组成的数组:
const x = ref(0)
const y = ref(0)
// 单个 ref
watch(x, (newX) => {
console.log(`x is ${newX}`)
}) // getter 函数
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
不能直接侦听响应式对象的属性值
2. 深层侦听器
直接给 watch() 传入一个响应式对象,会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发:
ps:除非直接直接修改这个相应式对象,否则newValue 此处和 oldValue 是相等的
const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {
// 在嵌套的属性变更时触发
// 注意:`newValue` 此处和 `oldValue` 是相等的
// 因为它们是同一个对象!
})
obj.count++
相比之下,一个返回响应式对象的 getter 函数,只有在返回不同的对象时,才会触发回调:
(可以显式地加上 deep 选项,强制转成深层侦听器)
watch( () => state.someObject, (newValue, oldValue) => {
// 仅当 state.someObject 被替换时触发
},
{
//deep: true
//immediate: true
})
3.即时回调的侦听器
watch 默认是懒执行的:仅当数据源变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。举例来说,我们想请求一些初始数据,然后在相关状态更改时重新请求数据。
我们可以通过传入 immediate: true 选项来强制侦听器的回调立即执行.
4.watchEffect()
watchEffect() 允许我们自动跟踪回调的响应式依赖,watchEffect回调会立即执行,不需要指定 immediate: true
watchEffect() 的好处:
1.对于有多个依赖项的侦听器来说,使用 watchEffect() 可以消除手动维护依赖列表的负担。2.如果你需要侦听一个嵌套数据结构中的几个属性,watchEffect() 可能会比深度侦听器更有效,因为它将只跟踪回调中被使用到的属性,而不是递归地跟踪所有的属性。
注意:watchEffect 仅会在其同步执行期间,才追踪依赖。在使用异步回调时,只有在第一个 await 正常工作前访问到的属性才会被追踪。
watchEffect(async () => {
// 这种情况 输出两次
//0
//1
// if (test.value <= -1) {
// return
// }
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)
// 这种情况 只输出一次
//0
if (test.value <= -1) {
return
}
console.log(test.value)
data.value = await response.json()
})
5.回调的触发时机
默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。
如果想在侦听器回调中能访问被 Vue 更新之后的 DOM,你需要指明 flush: 'post' 选项,或后置刷新的 watchEffect() 有个更方便的名 watchPostEffect():
watch(source, callback, { flush: 'post' })
watchEffect(callback, { flush: 'post' })
watchPostEffect(() => { /* 在 Vue 更新后执行 */ })
6.停止侦听器
在 setup() 或 <script setup> 中用同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止。
一个关键点是,侦听器必须用同步语句创建:如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏。
const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时 unwatch()
请尽可能选择同步创建。如果需要等待一些异步数据,你可以使用条件式的侦听逻辑:
// 需要异步请求得到的数据
const data = ref(null)
watchEffect(() => {
if (data.value) {
// 数据加载后执行某些操作...
}
})