你是不是经常在写Vue时纠结:这个功能到底该用computed还是watch?明明两个都能实现,但用哪个更合适?
有时候选了watch,代码变得又长又难维护;有时候用了computed,却发现根本监听不到数据变化。别急,今天我就带你彻底搞懂这两个家伙,让你的代码既优雅又高效!
先来认识一下这三位"候选人"
在Vue的世界里,处理数据变化主要有三种方式:计算属性(computed)、方法(methods)和侦听器(watch)。它们各自有不同的"职责范围"。
计算属性就像个聪明的助手,它会根据依赖的数据自动计算出一个新值,而且还会缓存结果,只有依赖变了才会重新计算。
方法就是个老实人,你叫它一次它就干一次活,不管数据变没变。
侦听器则像个保安,专门盯着某个数据的变化,一旦有动静就立即执行你交代的任务。
听起来有点抽象?来看个实际例子你就明白了:
// 计算属性的用法
computed: {
fullName() {
return this.firstName + ' ' + this.lastName
}
}
// 方法的用法
methods: {
getFullName() {
return this.firstName + ' ' + this.lastName
}
}
// 侦听器的用法
watch: {
firstName(newVal) {
this.fullName = newVal + ' ' + this.lastName
},
lastName(newVal) {
this.fullName = this.firstName + ' ' + newVal
}
}
看到区别了吗?计算属性只需要一个函数就搞定了全名,而侦听器需要分别监听姓和名的变化,代码量多了不少。
什么时候该用谁?
记住这个选择原则:能用computed就尽量用computed,需要异步或执行副作用时用watch。
计算属性的最佳使用场景
计算属性特别适合这两种情况:
- 模板中的复杂表达式:当模板中有太多逻辑时,应该用计算属性来简化
// 不好的写法:模板里写太多逻辑
<template>
<div>{{ message.split('').reverse().join('') }}</div>
</template>
// 好的写法:用计算属性
<template>
<div>{{ reversedMessage }}</div>
</template>
<script>
export default {
computed: {
reversedMessage() {
return this.message.split('').reverse().join('')
}
}
}
</script>
- 依赖多个数据源的计算:当一个值需要根据多个数据来计算时
computed: {
// 购物车总价,依赖商品列表和每种商品的数量
totalPrice() {
return this.products.reduce((total, product) => {
return total + product.price * product.quantity
}, 0)
}
}
侦听器的正确打开方式
watch通常在以下情况使用:
- 数据变化时需要执行异步操作
- 数据变化时需要执行副作用(如调用API)
- 需要监听路由变化等非响应式数据
watch: {
// 搜索关键词变化时,延迟500毫秒后搜索
searchKeyword(newVal) {
if (this.timer) {
clearTimeout(this.timer)
}
this.timer = setTimeout(() => {
this.doSearch(newVal)
}, 500)
}
}
那些年我们踩过的watch坑
watch虽然强大,但用不好就会掉坑里。最常见的就是深度监听和立即执行的问题。
deep选项:监听对象内部变化
默认情况下,watch只监听对象本身的赋值变化,不监听对象内部属性的变化。这时候就需要deep选项出场了。
data() {
return {
user: {
name: '张三',
address: {
city: '北京'
}
}
}
},
watch: {
// 普通监听:只有整个user对象被重新赋值时才会触发
user(newVal) {
console.log('用户信息变了')
},
// 深度监听:user内部的任何属性变化都会触发
user: {
handler(newVal) {
console.log('用户信息深度变化')
},
deep: true // 开启深度监听
}
}
但要注意:深度监听性能开销较大,尽量不要对大对象使用。
immediate选项:初始立即执行
有时候我们希望watch在组件创建时就立即执行一次,这时候就需要immediate选项。
watch: {
// 默认不会立即执行
someData(newVal) {
console.log('数据变化了')
},
// 设置immediate: true后会立即执行一次
someData: {
handler(newVal) {
console.log('数据变化或初始执行')
},
immediate: true
}
}
Vue 3中的watch和watchEffect
如果你已经开始用Vue 3,那么watch的用法有了一些新变化,还多了一个watchEffect。
watch的Composition API用法
import { ref, watch } from 'vue'
export default {
setup() {
const count = ref(0)
const doubleCount = ref(0)
// 监听count的变化
watch(count, (newVal, oldVal) => {
doubleCount.value = newVal * 2
})
return { count, doubleCount }
}
}
watchEffect:自动追踪依赖
watchEffect是Vue 3的新特性,它会自动追踪函数内使用的响应式数据,任何被使用的数据发生变化都会触发回调。
import { ref, watchEffect } from 'vue'
export default {
setup() {
const count = ref(0)
const doubleCount = ref(0)
// 自动追踪count,count变化就执行
watchEffect(() => {
doubleCount.value = count.value * 2
})
return { count, doubleCount }
}
}
watch和watchEffect的主要区别是:watch需要明确指定监听的数据,而watchEffect会自动收集依赖。
实战避坑指南
说了这么多理论,来看几个实际开发中容易踩的坑:
坑1:在watch中修改监听的数据
这会导致无限循环,要特别小心!
// 错误示范:会导致无限循环
watch: {
count(newVal) {
this.count = newVal + 1 // 修改监听的数据,又会触发watch...
}
}
// 正确做法:添加条件判断
watch: {
count(newVal) {
if (newVal < 10) {
this.count = newVal + 1
}
}
}
坑2:忘记处理异步操作的竞态条件
当连续触发多次异步操作时,需要确保最终结果是正确的。
watch: {
async searchQuery(newVal) {
// 取消之前的请求
if (this.currentRequest) {
this.currentRequest.cancel()
}
// 发起新请求
this.currentRequest = this.$http.get('/search', { params: { q: newVal } })
this.results = await this.currentRequest
}
}
总结一下
说了这么多,我们来个简单总结:
- computed:用于派生数据,有缓存,依赖变化才重新计算
- methods:用于事件处理或需要主动调用的函数
- watch:用于数据变化时需要执行副作用或异步操作的场景
记住这个选择口诀:想要派生新数据,computed是第一选择;想要响应数据变,watch来帮你搞定;需要主动调用的,methods才是正解。
现在你已经掌握了computed和watch的正确打开方式,下次写代码时就不会再纠结了吧?
你在使用computed和watch时还遇到过哪些坑?欢迎在评论区分享你的经历和解决方案!
原文链接:https://mp.weixin.qq.com/s/8-jMcOGNTIe-1sI0VaTUag
349

被折叠的 条评论
为什么被折叠?



