Vue3 计算属性与监听器:computed、watch、watchEffect 用法解析

在 Vue 开发中,计算属性(computed)和监听器(watch、watchEffect)是处理响应式数据的核心工具。Vue3 不仅保留了 Vue2 中的核心功能,还新增了 watchEffect 简化监听逻辑。本文将通过实战案例,详细讲解三者的用法、差异及最佳实践,帮你高效处理数据依赖与更新逻辑。

一、计算属性 computed:依赖数据的 "自动更新器"

计算属性基于其依赖的响应式数据自动计算结果,且会缓存计算结果 ------ 只有当依赖数据变化时,才会重新计算,避免重复执行复杂逻辑。

1. 基础用法:只读计算属性

最常见的场景是基于已有数据生成新数据(如拼接字符串、计算总和)。

实战示例:拼接全名

html 复制代码
<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>

核心特点

  • 缓存机制:若 firstNamelastName 未变化,多次访问 fullName 只会返回缓存结果,不会重新执行函数
  • 响应式依赖:自动追踪依赖的响应式数据,依赖变化时自动更新
  • 只读性:默认情况下,计算属性是只读的,无法直接修改(如 fullName.value = '李·四' 会报错)
2. 进阶用法:可读写计算属性

当需要通过计算属性修改依赖数据时,可定义 get(读取)和 set(修改)方法,实现 "双向绑定"。

实战示例:通过全名修改姓和名

html 复制代码
<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 支持监听 refreactive 对象、函数返回值等多种数据类型。

1. 场景一:监听 ref 基本类型数据

监听 ref 定义的基本类型数据(如 numberstring),直接传入数据即可。

html 复制代码
<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

javascript 复制代码
<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

javascript 复制代码
<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. 场景四:监听对象中的单个属性

若只需监听对象中的某个属性(而非整个对象),需通过函数返回值的方式指定监听目标,避免不必要的深度监听,提升性能。

javascript 复制代码
<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. 场景五:监听多个数据

同时监听多个响应式数据,将它们放入数组中即可,回调函数的参数会按数组顺序返回新值和旧值。

html 复制代码
<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. 基础用法:自动追踪依赖
html 复制代码
<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 会在创建时立即执行一次,之后依赖变化时再次执行
  • 自动追踪:无需指定监听目标,函数内部用到的响应式数据都会被追踪
  • 无新旧值:回调函数没有 newValoldVal 参数,仅关注当前值
2. 停止监听

watchEffect 返回一个停止函数,调用该函数可手动停止监听,避免内存泄漏(如组件卸载时)。

html 复制代码
<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) 无(仅当前值)
适用场景 数据转换、拼接、过滤 精准监听、需新旧值对比 依赖较多、无需新旧值对比
最佳实践建议
  1. 数据计算用 computed :当需要基于已有数据生成新数据(如全名、总价、过滤列表),且不需要副作用时,优先用 computed,利用其缓存机制提升性能。

  2. 精准监听用 watch :当需要监听特定数据,且需要对比新旧值(如表单值变化、路由参数变化),或需要手动控制深度监听时,用 watch

  3. 自动追踪用 watchEffect :当需要执行副作用(如发送请求、操作 DOM),且依赖较多或不明确时,用 watchEffect,简化代码(如页面初始化时加载数据)。

  4. 避免过度使用 watch :不要用 watch 实现计算属性的功能(如 watch(count, () => { doubleCount.value = count.value * 2 })),这种场景下 computed 更简洁、高效。

  5. 清理副作用 :在 watchwatchEffect 中若有副作用(如定时器、事件监听),需在组件卸载时清理(如 onUnmounted 中停止定时器),避免内存泄漏。

五、总结

computed、watch、watchEffect 是 Vue3 处理响应式数据的三大核心工具,它们各有侧重:

  • computed 是 "数据加工机",专注于数据计算与缓存;
  • watch 是 "精准哨兵",专注于特定数据的变化监听;
  • watchEffect 是 "智能管家",专注于自动追踪依赖的副作用执行。
相关推荐
_Sem3 小时前
KMP实战:从单端到跨平台的完整迁移指南
android·前端·app
Carry3453 小时前
React 与 Vue 开发差异——CSS 样式
前端
前端九哥3 小时前
我删光了项目里的 try-catch,老板:6
前端·vue.js
2301_764441333 小时前
身份证校验工具
前端·python·1024程序员节
顽疲3 小时前
SpringBoot + Vue 集成阿里云OSS直传最佳实践
vue.js·spring boot·阿里云
4Forsee3 小时前
【Android】View 事件分发机制与源码解析
android·java·前端
VT.馒头3 小时前
【力扣】2725. 间隔取消
javascript·leetcode·1024程序员节
一 乐4 小时前
车辆管理|校园车辆信息|基于SprinBoot+vue的校园车辆管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·毕设·车辆管理
百锦再4 小时前
Python、Java与Go:AI大模型时代的语言抉择
java·前端·vue.js·人工智能·python·go·1024程序员节