【Vue3基础】Vue3中的 computed、watch 和 watchEffect

Vue3中,computed用于计算属性,仅在依赖变化时更新;watch用于监听响应式数据并在变化时执行回调,支持深度监听和调整触发时机;watchEffect则是一个更简洁的监听方式,自动追踪依赖并立即执行,适用于简单的副作用逻辑。watchEffect的回调可以在同步或异步中运行,但应避免在其中进行大量的数据处理或触发大量更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Vue3中的 computed、watch 和 watchEffect

1.watch

1.1 概念

watch用于侦听一个或多个响应式数据源,并在数据源发生变化时调用所给出的回调函数。

官方文档 — watch

1.2 用法

实际上,在 Vue2 中,对于watch的使用是很常见的,所以其基本使用这里就不说了,主要探讨 Vue3 中相较于之前的变化。

在 Vue3 中,原本常用的两种方法,仅适用于用ref创建的响应式数据。

import { watch } from 'vue'

// 监视ref所定义的一个响应式数据
watch(sum,(newValue,oldValue)=>{
	console.log('sum变了',newValue,oldValue)
},{ immediate: true,deep: true })
    
// 监视ref所定义的多个响应式数据,newValue 和 oldValue都是数组
watch([sum,msg],(newValue,oldValue)=>{
	console.log('sum或msg变了',newValue,oldValue)
})

结合官方文档,可以总结如下:

  1. 第一个参数:需要侦听的属性,必须为一个响应式数据。如果不是,则会报错。
  2. 第二个参数:一个回调函数,即如何处理侦听属性的逻辑代码。
  3. 第三个参数:可选参数,类型为对象。
  4. watch可以一次侦听多个响应式属性,只需要将它们放在一个数组中即可。

对于watch中的第三个参数,其要求传入一个对象,其中包括:

  • immediate — 是否立即执行,在 Vue 中,watch默认为懒侦听,即仅在侦听对象发生变化时执行回调。该属性默认为false。当将该属性置为true时,会在**setup函数执行阶段**就会调用一次回调函数,且第一次调用时,回调函数中的oldValueundefined
  • deep — 是否开启深度侦听,即当侦听对象为一个具有多层次的响应式对象,其内层属性发生变化时是否能够被侦听到并触发回调。

当侦听对象为一个由ref处理得到的响应式对象,此时如果想要实现深度侦听,需要手动开启深度解析器deep: true。例如:

import { ref,watch } from 'vue'

const deepObj = ref({
  a: [1,3,5,6,[8,7,9]],
  b: '66'
})

setTimeout(()=>{
  deepObj.value.a[4][0] = 9
  console.log(deepObj.value)
},3000)

watch(deepObj,(newValue,oldValue) => {
  console.log(oldValue)
  console.log(newValue)
},{ deep: true })
// 这种情况下不加deep: true,无法触发回调函数
// 加deep: true,触发回调函数

这里实际上也可以看到,当侦听一个对象时,无法正确获取到 oldValue。因为 oldValue 和 newValue 实际上都是是对侦听对象的一个引用,所以实际上此时这两者都指向同一个对象,所以输出是相同的,这也是watch的一个小坑吧。

而当侦听属性是一个由reactive处理得到的响应式对象的话,就会强制开启深度侦听,此时deep配置是无效的。

import { watch, reactive } from 'vue'

const deepObj = reactive({
  a: [1,3,5,6,[8,7,9]],
  b: '66'
})

setTimeout(()=>{
  deepObj.a[4][0] = 9
  console.log(deepObj)
},3000)

watch(deepObj,(newValue,oldValue) => {
  console.log(oldValue)
  console.log(newValue)
})

// 没写deep: true,但还是触发了回调函数
// 加上deep: false,你会发现配置无效,回调函数依旧被调用
1.3 flush与回调的触发时机

总所周知,Vue 组件的更新是异步的,当我们所侦听的属性发生变化时,就可能触发 Vue 组件的更新和侦听回调。

而在默认情况下,侦听的回调会发生在 Vue 组件更新之前

也就是说,此时我们在侦听回调函数中,所能够访问到的 DOM 是 Vue 组件更新之前的状态。

而实际上,我们在watch的第三个参数中,还可以配置一个属性:flush。通过这个属性,我们可以调整回调函数的调用时机。

flush 可以设定三种值'pre'(默认值)‘post’‘sync’

将其设置为post,即可实现将侦听回调触发时机改为Vue组件更新之后

1.4 watch侦听reactive处理的响应式数据

需要注意以下两点:

  1. 此时无法获取 oldValue。
  2. 强制开启深度监视(deep配置无效)
// 监视reactive所定义的一个响应式数据
watch(data,(newValue,oldValue)=>{
	console.log('data变化',newValue,oldValue)
})

// 监视reactive所定义的一个响应式数中的某个属性,此时可以获得 oldValue。
watch(()=>data.name,(newValue,oldValue)=>{
	console.log('data变化',newValue,oldValue)
})

// 监视reactive所定义的一个响应式数中的某些属性(同时侦听多个)
watch([()=>data.name,()=>data.age],(newValue,oldValue)=>{
	console.log('data变话',newValue,oldValue)
})

但需要注意的是,这里有一个特殊情况:

// 监视reactive所定义的一个响应式数中的对象属性,此时无法获得oldValue,且 deep 配置有效
// 这也是官方文档深度侦听器有介绍的特殊情况
watch(()=>data.job,(newValue,oldValue)=>{
	console.log('data变了',newValue,oldValue)
},{deep: true})

2.watchEffect

2.1 描述

立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。watchEffectwatch更简易易用,但是高度的封装意味着它更抽象,没有watch好理解。

官方文档

2.2 用法
import { watchEffect } from 'vue'
// 上来就回调一次
watchEffect(()=>{
    const x1 = sum.value
    console.log('watchEffect所指定的回调执行了')
})

结合官方文档,总结如下:

  1. 不用指明监视哪个属性,监视的回调中用到哪个属性,就会自动追踪哪个属性(和computed类似)
  2. 非惰性侦听,(与默认的watch相反),但是在配置对象中没有像immediate这样的属性控制惰性或非惰性侦听,这也意味着非惰性侦听这个特性无法被更改。
  3. 最多只有两个参数,第一个参数为副作用函数(可以理解为回调函数),第二个是可选参数为配置对象,里面属性下面再说。
  4. 返回值是一个用来停止该副作用的函数。(可以理解为停止侦听器)

对于watchEffect中的第二个参数,要求传入一个对象,其属性有: flush、onTrack / onTrigger

这里主要说flush — 与watch中相同,控制回调执行的时机。

flush: posh时,情况和watch的一样,用于控制回调时机。有更方便的别名,用法 ⬇

import { watchPostEffect } from 'vue'

watchPostEffect(() => {
  
})

对于flush: sync,也有更方便的别名,用法⬇

import { watchSyncEffect } from 'vue'

watchSyncEffect(() => {
  
})

而对于flush: sync,官方描述如下:

某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器。这可以通过设置 flush: 'sync' 来实现。然而,该设置应谨慎使用,因为如果有多个属性同时更新,这将导致一些性能和数据一致性的问题。

“在响应式依赖发生改变时立即触发侦听器”,可能有朋友对这点会有疑惑,我每次使用watchwatchEffect侦听响应式属性,每次在打印台都能马上看到打印,不就是说明立即触发了侦听器的副作用函数吗?其实并非如此,这里有必要解释一下,这里缓存的意义。

缓存的概念相信大家都并不陌生,但是此处Vue中的缓存是指什么呢?

我们都早早的在Vue2时学习了**$nextTick的意义,以及Vue更新组件的异步渲染**。同样的, Vue3中也有nextTick这一和$nextTick意义作用对等的东西,甚至熟悉 JS 事件循环机制的朋友知道 eventloop 的一个循环我们称之为 tick

熟悉上面概念的朋友,理解这里的缓存就十分轻松了,原来 Vue3 会对watchEffect侦听器的副作用函数中的响应式依赖数据作缓存处理

watchEffect侦听器可能会同时追踪多个响应式数据,当多个响应式数据在同一时间发生变化时,内部会“稍作等待”,观察是否有其他响应式数据发生变化需要触发副作用函数,而最终的结果就是打印台只触发一次副作用函数,反映最终结果。在数据量少、逻辑简单时,“稍作等待”的时间十分短,就会让我们产生“立即触发了侦听器”的错觉。

言归正传,flush: sync就是希望打破这一缓存等待的机制,让其真正意义上的"立即触发侦听器",所以需要谨慎使用。

2.3 watchEffect停止侦听器。

在上面总结的第四点中,说到过watchEffect会返回一个函数,用于停止该副作用函数。

即:

const stop = watchEffect(() => {})

// 当不再需要此侦听器时:
stop()

那这个函数有哪些应用的场景呢?

什么应用场景呢?

通常watchEffect需用同步语句创建,他会绑定到当前组件上,在组件销毁时会自动停止侦听,防止内层泄露。同步语句创建其实就是上面的写法,直接在setup中创建注册。

如果用异步语句创建,如用setTimeout函数包裹,则不会自动停止,需要手动停止。

2.4 watchEffect副作用清除

官方给出的例子如下:

watchEffect(async (onCleanup) => {
  const { response, cancel } = doAsyncWork(id.value)
  // `cancel` 会在 `id` 更改时调用
  // 以便取消之前
  // 未完成的请求
  onCleanup(cancel)
  data.value = await response
})

主要要理解好 当中onCleanup的意义,当然这只是个参数名你取什么名字都可以,它是一个用于注册副作用清理的回调函数。

该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。一般是做一些异步请求连发限制或取消请求的操作,保证请求数据的完整和准确性。

3.computed

官方对于computed的描述如下:

接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。

3.1 computed的特性
  1. 在 getter 函数中,在追踪的响应式依赖没有发生变化时,返回都会是上次缓存的数据。(计算属性和方法的区别)
  2. 只有在computed中的响应式依赖发生变化时,才会再次进行运算。

4.三者的区别

  • 参数上,computed只接受一个 getter 函数,watchwatchEffect则不同,需要传入回调的函数。
  • 返回值computed必须要有个返回值,他会是一个只读的ref对象(最佳实践场景),watchwatchEffect的返回会是一个用于停止侦听的函数。
  • computedwatchEffect都会自动追踪响应式依赖,watch则需要主动追踪响应式依赖。
  • 使用上,computed着重计算watchwatchEffect着重侦听。从参数上考究,computed没有像watchwatchEffect的配置对象参数去支持调整函数触发时机,官方也说了,computed的函数中不能做异步请求或者更改 DOM,而watchwatchEffect则相反,十分支持完成类似操作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值