computed、watch、watchEffect 区别
一、计算属性
computed()
计算属性,依赖其他数据,根据依赖的数据进行计算得到的结果。
特点:
- 接收一个
getter
函数,返回值为一个计算属性ref
; - 计算属性会自动追踪响应式依赖;
- 计算属性会缓存,只有当依赖数据发生变化时才会重新计算;
- 计算属性默认是只读的,可写属性通过
getter
和setter
创建;
二、侦听器
1.watch()
特点:
- watch 的第一个参数可以是
ref(包括计算属性)
、reactive
、getter函数(() => x.value + y.value)
、多个数据源组成的数组
,第二个参数是回调函数,第三个参数是配置对象; - 不能直接侦听响应式对象的属性值,使用
() => obj.count
,代替obj.count
; - 回调函数可接受 3 个参数,新值、旧值,以及一个用于注册副作用清理的回调函数;
配置对象
deep
- 当侦听属性是一个是由 ref 处理的响应式对象的话,就需要手动开启深度侦听 deep: true,否则 watch 无法侦听到里面属性的变化;
- 当侦听属性是一个是由
reactive
处理的响应式对象的话,会强制开启深度侦听,deep
配置无效(存在特殊情况),此时newValue
和oldValue
相等(同一个对象); - 特殊情况:
监视 reactive 所定义的一个响应式数据中的对象属性,此时 deep 配置有效// job为对象属性 watch( () => data.job, (newValue, oldValue) => { console.log('data变了', newValue, oldValue) }, { deep: true } )
immediate
默认懒执行,当侦听属性发生变化时才会执行回调函数,可使用immediate: true
强制立即执行;
once
使用once:true
,表示只侦听一次;
flush
Vue 组件更新是异步的,当侦听属性发生变化时,就可能触发 Vue 组件的更新和侦听回调。默认情况下,侦听回调的触发会在 Vue 组件更新之前,设定flush: post
,可以将侦听回调触发时机改为 Vue 组件更新之后。
值:'pre'
(默认值)、'post'
、'sync'
副作用清理
使用场景:取消过时请求(当id已经发生变化,但是请求还未结束时,取消掉之前的请求)
import { watch, onWatcherCleanup } from 'vue'
watch(id, newId => {
const controller = new AbortController()
fetch(`/api/${newId}`, { signal: controller.signal }).then(() => {
// 回调逻辑
})
onWatcherCleanup(() => {
// 终止过期请求
controller.abort()
})
})
注意:
–onWatcherCleanup 仅在 Vue 3.5+ 中支持,并且必须在 watchEffect 效果函数或 watch 回调函数的同步执行期间调用:不能在异步函数的 await 语句之后调用它;
–在3.5版本之前,使用watch回调函数的第三个参数
/watchEffect回调函数的第一个参数
作为副作用清理回调
。此外,通过函数参数传递的 onCleanup 与侦听器实例相绑定,因此不受 onWatcherCleanup
的同步限制。
watch(id, (newId, oldId, onCleanup) => {
// ...
onCleanup(() => {
// 清理逻辑
})
})
watchEffect((onCleanup) => {
// ...
onCleanup(() => {
// 清理逻辑
})
})
返回
返回一个函数,用于停止侦听器。
const number = ref(1)
// 定时触发此方法
const upNumber = () => {
number.value++
}
setInterval(() => {
upNumber()
}, 1000)
const stop = watch(number, () => {
console.log(number.value)
// 停止侦听
stop()
})
注意:副作用函数中使用异步语句才需要手动停止,同步情况下会随着组件销毁自动停止。
2.watchEffect()
特点:
- 第一个参数为回调函数,第二个为配置对象;
- 自动追踪响应式依赖;
- 非惰性侦听,且不可被更改;
- 回调函数只接受一个用于注册副作用清理的回调函数;
配置对象
flush
和 watch 的一样,用于控制回调时机,直接使用别名 watchPostEffect、watchSyncEffect;
import { watchSyncEffect } from 'vue'
watchSyncEffect(() => {})
Vue3 会对 watchEffect 侦听器的副作用函数响应式依赖数据作缓存处理,watchEffect 侦听器可能会同时追踪多个响应式数据
,当多个响应式数据在同一时间发生变化时,内部会“稍作等待”
,观察是否有其他响应式数据发生变化需要触发副作用函数,而最终的结果就是控制台只触发一次
副作用函数,反映最终结果。flush: sync
用于打破这一缓存等待的机制,让其真正意义上的"立即触发侦听器"
;
import { ref, watch, watchSyncEffect } from 'vue'
const number1 = ref(1)
const number2 = ref(10)
// 点击按钮触发此方法
const upNumber = () => {
number1.value++
number2.value++
}
setTimeout(() => {
upNumber()
}, 3000)
/*
watch(
[number1, number2],
() => {
console.log(number1.value)
console.log(number2.value)
},
{ flush: 'sync' }
)
*/
// watchSyncEffect
watchSyncEffect(() => {
console.log(number1.value)
console.log(number2.value)
})
上述代码中没有添加 flush: 'sync'
,副作用函数只触发一次,添加 flush: 'sync'
,副作用函数触发两次。
运行结果:
返回
同 watch
注意:watchEffect
侦听器会立即执行,可能会有如下报错:
解决:使用flush:post
配置项
侦听器官方参考链接
https://cn.vuejs.org/guide/essentials/watchers#callback-flush-timing