【vue 3 之 `watch` & `$watch` 侦听器的使用与讲解】各侦听源&各配置参数案例讲解

时间:2025-01-19 07:29:47

$watch()、watch()的使用方式与功能 完全等效,在此案例只写composition api的watch()

本章涉及内容:概念、watch的使用方式、功能讲解。

由于3.4版本watch新增了改动,重新修改了下本文章。

watch监听的选项 ts类型:

interface WatchOptions extends WatchEffectOptions {
  immediate?: boolean // 默认:false
  deep?: boolean // 默认:false
  flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
  once?: boolean // 默认:false (3.4+)
}
  • immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined
  • deep:true为监听所有嵌套的属性。(ps: 未主动声明deep时,如果源是reactive对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器。)
  • flush:调整回调函数的刷新时机。参考回调的刷新时机及 watchEffect()
  • onTrack / onTrigger:调试侦听器的依赖。参考调试侦听器
  • once: 回调函数只会运行一次。侦听器将在回调函数首次运行后自动停止。 

概念:

  • watch API 与选项式 API this.$watch (以及相应的 watch 选项) 完全等效。
  • watch 需要侦听特定的数据源,并在单独的回调函数中执行副作用。默认情况下,它也是惰性的——即回调仅在侦听源发生变化时被调用。
  • 缓冲回调:缓冲回调不仅可以提高性能,还有助于保证数据的一致性。在执行数据更新的代码完成之前,侦听器不会被触发。简单来说,同步修改数据时,修完操作执行完毕后才会触发回调。注意是同步!所以异步操作的时候,要注意多次触发watch的问题。(所有同步操作为1次,异步操作有几次就触发多少次监听!!)
  • 直接传入ref对象,会自动解包 .value

功能1:停止侦听

此处监听的ref对象,传入的是ref对象则会自动解包 .value(基于源码的解释)

<script lang='ts' setup>
import { watch,ref } from 'vue';
let count = ref(0)
//! 关于 watch() 返回的 StopHandle 函数,调用stop()将会停止侦听。
let stop = watch(count, (newValue, oldValue,InvalidateCbRegistrator) => {
  ('watch : count-:' + newValue, oldValue)
})
setTimeout(() => stop(), 3333); // stop() 停止侦听
</script>

 功能2:监听 reactive() 的对象:

直接监听reactive声明的proxy对象,最终vue会默认赋值为true,所以自己传什么都没用。(基于源码的解释)

订正:vue3.4版本发生改动,传false则是监听该reactive对象的`根级别`属性的变更,如果要监听更深层的任意个属性变更也触发watch,则deep需要传true。

<script lang='ts' setup>
import { watch,reactive } from 'vue';
let proxy1 = reactive({})
watch(proxy1, (newValue, oldValue) => {
  ('proxy1--',newValue , oldValue )
},{
  deep: false
})
</script>

 功能3:监听嵌套对象的某个属性 (传入函数)

单独监听嵌套的某个属性 则需要传入函数的返回值

<script lang='ts' setup>
import { watch,reactive } from 'vue';
// reactive  ref都行,看了 refs 篇章你也知道其原理
let proxy1 = reactive({t1:'嵌套数据'}) 
watch(()=>proxy1.t1, (newValue, oldValue) => {
  (newValue, oldValue)
})
</script>

功能4:监听多个源的形式 (传入数组)。 

<script lang='ts' setup>
import { watch,reactive } from 'vue';
let data1 = reactive({t1:'t1嵌套数据'}) 
let data2 = reactive({t2:'t2嵌套数据'}) 
watch([data1,data2], (newValue, oldValue) => {
  (newValue, oldValue)
})
</script>

 功能5:第三个参数的3种形式:

  1. flush 关于这个参数的讲解:首先 选项式api( watch:{} ) 、$watch() 、 watch()、watchEffect() 4个都能用!
  2. pre 模式下,指定的回调在模板渲染前被调用,所以立马获取对应的内容,将不会是最新的!!
  3. post 模式下,将回调推迟到模板渲染之后的,这时候用$ref获取对应内容则会是最新的,等于 pre 模式下用 nextTick()
  4. sync 就不多说了,回调改为同步调用,即取消 <缓冲回调> 这个功能
  5. 'pre' 和 'post' 回调使用队列进行缓冲,这也是为什么<同步>多次修改后,只触发最后一次监听回调的原因。
<script lang='ts' setup>
import { watch,reactive } from 'vue';
let data1 = reactive({t1:'t1嵌套数据'})
watch(data1, (newValue, oldValue) => {
  ('data1--',newValue, oldValue)
},{
  deep:false, // 用讲?不懂看选项API的watch
  immediate:false, // 用讲?不懂看选项API的watch
  flush:'post' // 'pre' | 'post' | 'sync'  // 默认值是'pre'
})

</script>

功能6:callback的第三个参数 onInvalidate > 注册失效回调
关于这个的解释,请移步我的watchEffect()文章,再来看这第六点,这样理解起来最佳!

<script lang='ts' setup>
import { watch,reactive } from 'vue';
let data1 = reactive({t1:'t1嵌套数据'})
let stop = watch(data1, (newValue, oldValue,InvalidateCbRegistrator) => {
  InvalidateCbRegistrator(()=>{
    ('执行了');
  })
})
data1.t1 = 1
settimeout(() => data1.t1 = 321, 1500)
setTimeout(() => stop(), 3333); // stop() 停止侦听
</script>

该注册回调的触发机制:

  • 副作用即将重新执行时(第一次不执行)
  • 侦听器被停止 (如果在 setup() 或生命周期钩子函数中使用了 watchEffect,在组件卸载时会自动停止侦听。)

与 watchEffect 共享的行为

watch 与 watchEffect共享停止侦听,清除副作用 (相应地 onInvalidate 会作为回调的第三个参数传入)、副作用刷新时机和侦听器调试行为。

特性7:

组件创建时的生命周期里同步执行的侦听会被收集,组件销毁时会自动销毁侦听器。
组件创建完后的再创建的侦听器,需要自己手动销毁,


QQ交流群:522976012  ,欢迎来玩。

聚焦vue3,但不限于vue,任何前端问题,究其本质,值得讨论,研究与学习。