Vue 3 中 computed 与 watch 深度解析
在 Vue 3 组合中,响应式工具的类型安全使用至关重要。以下是详细说明
一、watch 侦听器
1. 基础类型监听
<template>
<div>实际参数1={{count}}</div>
<div>
<button @click="count++">点击</button>
</div>
</template>
<script setup lang="ts">
import {
reactive,
ref,
watch
} from "vue";
const count = ref<number>(0)
const state = reactive({items: [] as string[]});
watch(count, (newVal:number, oldVal:number) => {
state.items.push(String(count.value))
console.log('newVal, oldVal === ', newVal, oldVal)
})
watch(() => state.items, () => {
console.log('state.items ===', state.items)
},{deep: true})
</script>
2. 多源监听
<template>
<div>实际参数1={{count}}</div>
<div>
<button @click="count++">点击</button>
</div>
</template>
<script setup lang="ts">
import {
reactive,
ref,
watch
} from "vue";
const count = ref<number>(0)
const state = reactive({items: [] as string[]});
watch(count, (newVal:number, oldVal:number) => {
state.items.push(String(count.value))
console.log('newVal, oldVal === ', newVal, oldVal)
})
watch(() => state.items, () => {
console.log('state.items ===', state.items)
},{deep: true})
watch([count, state], ([newCount, newState]:[number, object], [oldCount, oldState]:[number, object])=> {
console.log('[newCount, newState], [oldCount, oldState] =', newCount, newState, oldCount, oldState)
})
</script>
3. 深度监听对象
<template>
<div>实际参数1={{product}}</div>
<div>
<button @click="product.price++">点击</button>
</div>
</template>
<script setup lang="ts">
import {
reactive,
watch
} from "vue";
interface Product {
id: number,
price: number,
name: string,
specs: {
color: string,
weight: number
}
}
const product = reactive<Product>({
id: 1,
price: 131,
name: 'Bwm',
specs: {
color: 'red',
weight: 80
}
})
watch(() => product.price, // 创建新引用触发深度监听
(newProduct,oldProduct) => {
console.log('newVal,oldVal === ', newProduct,oldProduct)
},{deep: true})
</script>
二、watchEffect 高级用法
watchEffect() 允许我们自动跟踪回调的响应式依赖,且会立即执行。
1. 基本用法
<template>
<div>实际参数1={{product}}</div>
<div>
<button @click="product.price++">点击</button>
</div>
</template>
<script setup lang="ts">
import {
reactive,
ref,
watch, watchEffect
} from "vue";
interface Product {
id: number,
price: number,
name: string,
specs: {
color: string,
weight: number
}
}
const product = reactive<Product>({
id: 1,
price: 131,
name: 'Bwm',
specs: {
color: 'red',
weight: 80
}
})
const totalPrice = ref<number>(0)
watchEffect(()=> {
totalPrice.value = product.price + 2
console.log('totalPrice === ', totalPrice)
})
</script>
2. 清理副作用
<template>
<div>实际参数1={{product}}</div>
<div>
<button @click="product.price+=10">点击</button>
</div>
</template>
<script setup lang="ts">
import {
reactive,
ref,
watchEffect,
onWatcherCleanup
} from "vue";
interface Product {
id: number,
price: number,
name: string,
specs: {
color: string,
weight: number
}
}
const product = reactive<Product>({
id: 1,
price: 131,
name: 'Bwm',
specs: {
color: 'red',
weight: 80
}
})
const totalPrice = ref<number>(0)
watchEffect(()=> {
totalPrice.value = product.price + 2
console.log('totalPrice === ', totalPrice)
onWatcherCleanup(() => {
console.log('onWatcherCleanup ===')
})
})
</script>
三、computed 计算属性
1. 对象类型计算
<template>
<div>实际参数1={{totalPrice}}</div>
<div>
<button @click="product.price+=10">点击</button>
</div>
</template>
<script setup lang="ts">
import {
reactive,
ref,
computed
} from "vue";
interface Product {
id: number,
price: number,
name: string,
specs: {
color: string,
weight: number
}
}
const product = reactive<Product>({
id: 1,
price: 131,
name: 'Bwm',
specs: {
color: 'red',
weight: 80
}
})
const totalPrice = computed<number>(()=>product.price += 10)
</script>
2. 可写计算属性
<template>
<div>{{fullName}}</div>
<div>姓名: <el-input v-model="fullName"/></div>
</template>
<script setup lang="ts">
import {
reactive,
ref,
computed
} from "vue";
const firstName = ref<string>('Jane')
const lastName = ref<string>('Smith')
const fullName = computed<string>({
get() {
return `${firstName.value} ${lastName.value}`
},
set(fullName:string) {
const [newFirstName, newLastName] = fullName.split(' ')
firstName.value = newFirstName
lastName.value = newLastName
}
})
</script>
四、computed vs watch vs watchEffect 对比
| 特性 | computed | watch | watchEffect |
|---|---|---|---|
| 目的 | 派生值 | 响应变化执行操作 | 自动追踪依赖执行操作 |
| 返回值 | ref对象 | 停止函数 | 停止函数 |
| 初始化执行 | 立即计算 | 可配置(immediate) | 立即执行 |
| 依赖声明 | 自动 | 显式指定 | 自动 |
| 缓存 | ✅ | ❌ | ❌ |
| 异步支持 | ❌ | ✅ | ✅ |
| 清理机制 | ❌ | ✅ | ✅ |
| 调试钩子 | ❌ | ✅ | ✅ |
| 适合场景 | 数据转换/格式化 | 精确控制的操作 | 自动追踪依赖的副作用 |
总结
-
computed:- 用于派生状态
- 具有缓存机制
- 适合数据转换和格式化
- 模板中优先使用
-
watch:- 用于执行副作用
- 提供精确控制
- 适合异步操作、DOM操作
- 需要显式声明依赖
-
watchEffect:- 自动追踪依赖
- 立即执行
- 适合多个依赖的简单副作用
- 提供清理机制
黄金法则:
- 需要派生值 → 用
computed - 需要响应变化执行操作 → 用
watch或watchEffect - 需要精确控制依赖 → 用
watch - 需要自动追踪多个依赖 → 用
watchEffect - 避免在
computed中产生副作用 - 总是在副作用中清理资源
通过合理选择和使用这些 API,可以构建出高效、可维护的 Vue 3 应用程序。