vue3 之 Watch WatchEffect 的使用和区别
曾经以为自己会用 watch
、 watchEffect
了,后来发现只是略懂皮毛。最近我就把 Vue3 的侦听器全面梳理了一下,分享给大家。看看有没有你不会的吧,一起学起来!
Watch
基本用法
当我们需要在数据变化时执行一些“副作用”:如更改 DOM、执行异步操作,我们可以使用 watch
函数:
1 | <script setup> |
watch()
一共可以接受三个参数,侦听数据源、回调函数和配置选项。
侦听数据源
watch
的第一个参数可以是不同形式的“数据源”,它可以是:
- 一个 ref
- 一个计算属性
- 一个 getter 函数(有返回值的函数)
- 一个响应式对象
- 以上类型的值组成的数组
1 | const x = ref(1) |
注意,你不能直接侦听响应式对象的属性值,例如:
1 | const obj = reactive({ count: 0 }); |
这里需要用一个返回该属性的 getter 函数:
1 | // 提供一个 getter 函数 |
回调函数
watch
的第二个参数是数据发生变化时执行的回调函数。
这个回调函数接受三个参数:新值、旧值,以及一个用于清理副作用的回调函数。该回调函数会在副作用下一次执行前调用,可以用来清除无效的副作用,如等待中的异步请求:
1 | const id = ref(1); |
watch
的返回值是一个用来停止该副作用的函数:
1 | const unwatch = watch(() => {}); |
注意:使用同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止。使用异步回调创建一个侦听器,则不会绑定到当前组件上,你必须手动停止它,以防内存泄漏。如下面这个例子:
1 | <script setup> |
配置选项
watch
的第三个参数是一个可选的对象,支持以下选项:
immediate
:在侦听器创建时立即触发回调。deep
:深度遍历,以便在深层级变更时触发回调。flush
:回调函数的触发时机。pre:默认,dom 更新前调用,post: dom 更新后调用,sync 同步调用。onTrack / onTrigger
:用于调试的钩子。在依赖收集和回调函数触发时被调用。
深层侦听器
直接给 watch()
传入一个响应式对象,会默认创建一个深层侦听器 —— 所有嵌套的属性变更时都会被触发:
1 | const obj = reactive({ count: 0 }); |
相比之下,一个返回响应式对象的 getter 函数,只有在对象被替换时才会触发:
1 | const obj = reactive({ |
当然,你也可以显式地加上 deep
选项,强制转成深层侦听器:
1 | watch( |
深层侦听一个响应式对象或数组,新值和旧值是相等的。为了解决这个问题,我们可以对值进行深拷贝。
1 | watch( |
注意:深层侦听需要遍历所有嵌套的属性,当数据结构庞大时,开销很大。所以我们要谨慎使用,并且留意性能。
watchEffect
watch()
是懒执行的:当数据源发生变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。当然使用 immediate
选项也能实现:
1 | const url = ref("https://..."); |
可以看到 watch
用到了三个参数,我们可以用 watchEffect
来简化上面的代码。watchEffect
会立即执行一遍回调函数,如果这时函数产生了副作用,Vue 会自动追踪副作用的依赖关系,自动分析出侦听数据源。上面的例子可以重写为:
1 | const url = ref("https://..."); |
watchEffect
接受两个参数,第一个参数是数据发生变化时执行的回调函数,用法和 watch
一样。第二个参数是一个可选的对象,支持 flush
和 onTrack / onTrigger
选项,功能和 watch
相同。
注意:watchEffect
仅会在其同步执行期间,才追踪依赖。使用异步回调时,只有在第一个 await
之前访问到的依赖才会被追踪。
watch vs. watchEffect
watch
和 watchEffect
的主要功能是相同的,都能响应式地执行回调函数。它们的区别是追踪响应式依赖的方式不同:
watch
只追踪明确定义的数据源,不会追踪在回调中访问到的东西;默认情况下,只有在数据源发生改变时才会触发回调;watch 可以访问侦听数据的新值和旧值。watchEffect
会初始化执行一次,在副作用发生期间追踪依赖,自动分析出侦听数据源;watchEffect
无法访问侦听数据的新值和旧值。
简单一句话,watch
功能更加强大,而 watchEffect
在某些场景下更加简洁。