Vue3 计算属性与监听器:computed、watch、watchEffect 用法解析
在 Vue 开发中,计算属性(computed)和监听器(watch、watchEffect)是处理响应式数据的核心工具。Vue3 不仅保留了 Vue2 中的核心功能,还新增了 watchEffect 简化监听逻辑。本文将通过实战案例,详细讲解三者的用法、差异及最佳实践,帮你高效处理数据依赖与更新逻辑。
一、计算属性 computed:依赖数据的 “自动更新器”
计算属性基于其依赖的响应式数据自动计算结果,且会缓存计算结果 —— 只有当依赖数据变化时,才会重新计算,避免重复执行复杂逻辑。
1. 基础用法:只读计算属性
最常见的场景是基于已有数据生成新数据(如拼接字符串、计算总和)。
实战示例:拼接全名
<template><div><input v-model="firstName" placeholder="姓"><input v-model="lastName" placeholder="名"><p>全名:{{ fullName }}</p></div>
</template><script setup>
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')// 只读计算属性:依赖 firstName 和 lastName
const fullName = computed(() => {console.log('计算全名(仅依赖变化时执行)')return `${firstName.value}·${lastName.value}`
})
</script>核心特点:
- 缓存机制:若
firstName和lastName未变化,多次访问fullName只会返回缓存结果,不会重新执行函数 - 响应式依赖:自动追踪依赖的响应式数据,依赖变化时自动更新
- 只读性:默认情况下,计算属性是只读的,无法直接修改(如
fullName.value = '李·四'会报错)
2. 进阶用法:可读写计算属性
当需要通过计算属性修改依赖数据时,可定义 get(读取)和 set(修改)方法,实现 “双向绑定”。
实战示例:通过全名修改姓和名
<template><div><input v-model="firstName" placeholder="姓"><input v-model="lastName" placeholder="名"><p>全名:{{ fullName }}</p><button @click="changeFullName">修改为“李·四”</button></div>
</template><script setup>
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')// 可读写计算属性
const fullName = computed({// 读取时执行get() {return `${firstName.value}·${lastName.value}`},// 修改时执行set(newValue) {// 拆分新值,更新依赖数据const [newFirst, newLast] = newValue.split('·')firstName.value = newFirst || ''lastName.value = newLast || ''}
})// 修改计算属性
const changeFullName = () => {fullName.value = '李·四'
}
</script>适用场景:
- 表单双向绑定:通过计算属性统一处理表单值的读写(如格式化日期、处理特殊字符)
- 复杂数据转换:修改计算属性时,自动同步更新多个依赖数据
二、监听器 watch:精准监听数据变化
watch 用于显式监听一个或多个响应式数据,当数据变化时执行自定义逻辑(如发送请求、更新 DOM)。Vue3 中的 watch 支持监听 ref、reactive 对象、函数返回值等多种数据类型。
1. 场景一:监听 ref 基本类型数据
监听 ref 定义的基本类型数据(如 number、string),直接传入数据即可。
<template><div><p>计数:{{ count }}</p><button @click="count.value++">+1</button></div>
</template><script setup>
import { ref, watch } from 'vue'
const count = ref(0)// 监听 ref 基本类型数据
watch(count, (newVal, oldVal) => {console.log(`计数从 ${oldVal} 变为 ${newVal}`)// 场景:计数达到 10 时发送通知if (newVal >= 10) {alert('计数已达到 10!')}
})
</script>2. 场景二:监听 ref 引用类型数据
监听 ref 定义的对象 / 数组时,默认仅监听地址变化(如替换整个对象)。若需监听内部属性变化,需手动开启 deep: true。
<script setup>
import { ref, watch } from 'vue'
const user = ref({ name: '张三', age: 18 })// 监听 ref 对象内部属性变化(需开启 deep)
watch(user, (newVal, oldVal) => {console.log('用户信息变化:', newVal)
}, { deep: true })// 修改对象内部属性(触发监听)
const changeAge = () => {user.value.age++
}// 替换整个对象(触发监听,无需 deep)
const changeUser = () => {user.value = { name: '李四', age: 20 }
}
</script>3. 场景三:监听 reactive 对象数据
监听 reactive 定义的对象 / 数组时,默认自动开启深度监听,无需手动设置 deep: true。
<script setup>
import { reactive, watch } from 'vue'
const user = reactive({ name: '张三', age: 18 })// 监听 reactive 对象(默认深度监听)
watch(user, (newVal) => {console.log('用户年龄变化:', newVal.age)
})// 修改对象内部属性(触发监听)
const changeAge = () => {user.age++
}
</script>4. 场景四:监听对象中的单个属性
若只需监听对象中的某个属性(而非整个对象),需通过函数返回值的方式指定监听目标,避免不必要的深度监听,提升性能。
<script setup>
import { reactive, watch } from 'vue'
const user = reactive({ name: '张三', age: 18, address: { city: '北京' } })// 监听 user.age(基本类型属性)
watch(() => user.age, (newVal) => {console.log('年龄变化:', newVal)
})// 监听 user.address.city(嵌套对象属性)
watch(() => user.address.city, (newVal) => {console.log('城市变化:', newVal)
})
</script>5. 场景五:监听多个数据
同时监听多个响应式数据,将它们放入数组中即可,回调函数的参数会按数组顺序返回新值和旧值。
<script setup>
import { ref, watch } from 'vue'
const firstName = ref('张')
const lastName = ref('三')// 监听多个数据
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {console.log(`姓从 ${oldFirst} 变为 ${newFirst}`)console.log(`名从 ${oldLast} 变为 ${newLast}`)console.log(`全名:${newFirst}${newLast}`)
})
</script>三、新特性 watchEffect:自动追踪依赖的 “懒人监听器”
watchEffect 是 Vue3 新增的监听器,它会自动追踪函数内部的响应式依赖,无需显式指定监听目标,适合 “依赖不明确” 或 “依赖较多” 的场景。
1. 基础用法:自动追踪依赖
<template><div><p>水温:{{ temp }}℃</p><p>水位:{{ height }}cm</p><button @click="temp++">水温+1</button><button @click="height++">水位+1</button></div>
</template><script setup>
import { ref, watchEffect } from 'vue'
const temp = ref(0)
const height = ref(0)// watchEffect:自动追踪 temp 和 height 依赖
watchEffect(() => {console.log(`当前水温:${temp.value}℃,水位:${height.value}cm`)// 场景:水温≥50℃ 或 水位≥20cm 时发送报警if (temp.value >= 50 || height.value >= 20) {alert('警告:水温或水位超标!')}
})
</script>核心特点:
- 自动执行:
watchEffect会在创建时立即执行一次,之后依赖变化时再次执行 - 自动追踪:无需指定监听目标,函数内部用到的响应式数据都会被追踪
- 无新旧值:回调函数没有
newVal和oldVal参数,仅关注当前值
2. 停止监听
watchEffect 返回一个停止函数,调用该函数可手动停止监听,避免内存泄漏(如组件卸载时)。
<script setup>
import { ref, watchEffect, onUnmounted } from 'vue'
const count = ref(0)// 创建 watchEffect 并获取停止函数
const stopWatch = watchEffect(() => {console.log('计数:', count.value)
})// 组件卸载时停止监听
onUnmounted(() => {stopWatch()
})// 手动停止监听(如计数达到 10 时)
const stopWhenCount10 = () => {if (count.value >= 10) {stopWatch()alert('监听已停止')}
}
</script>四、computed、watch、watchEffect 对比与最佳实践
| 特性 | computed | watch | watchEffect |
|---|---|---|---|
| 核心用途 | 依赖数据计算新值 | 监听特定数据,执行副作用 | 自动追踪依赖,执行副作用 |
| 依赖追踪 | 自动追踪依赖 | 显式指定监听目标 | 自动追踪函数内依赖 |
| 执行时机 | 依赖变化时计算 | 数据变化时执行 | 创建时立即执行,依赖变化再执行 |
| 缓存机制 | 有(依赖不变时返回缓存) | 无(每次变化都执行) | 无(每次依赖变化都执行) |
| 新旧值 | 无(仅返回当前值) | 有(newVal、oldVal) | 无(仅当前值) |
| 适用场景 | 数据转换、拼接、过滤 | 精准监听、需新旧值对比 | 依赖较多、无需新旧值对比 |
最佳实践建议
数据计算用 computed:当需要基于已有数据生成新数据(如全名、总价、过滤列表),且不需要副作用时,优先用
computed,利用其缓存机制提升性能。精准监听用 watch:当需要监听特定数据,且需要对比新旧值(如表单值变化、路由参数变化),或需要手动控制深度监听时,用
watch。自动追踪用 watchEffect:当需要执行副作用(如发送请求、操作 DOM),且依赖较多或不明确时,用
watchEffect,简化代码(如页面初始化时加载数据)。避免过度使用 watch:不要用
watch实现计算属性的功能(如watch(count, () => { doubleCount.value = count.value * 2 })),这种场景下computed更简洁、高效。清理副作用:在
watch和watchEffect中若有副作用(如定时器、事件监听),需在组件卸载时清理(如onUnmounted中停止定时器),避免内存泄漏。
五、总结
computed、watch、watchEffect 是 Vue3 处理响应式数据的三大核心工具,它们各有侧重:
computed是 “数据加工机”,专注于数据计算与缓存;watch是 “精准哨兵”,专注于特定数据的变化监听;watchEffect是 “智能管家”,专注于自动追踪依赖的副作用执行。
