Vue3 计算属性 (computed) 与监听属性 (watch)

本文基于 Vue3 主流的 <script setup> 语法糖编写,通俗易懂、知识点全覆盖、区分清晰,从概念、用法、特性、区别、使用场景到实战案例,带你彻底吃透 computed 和 watch,看完再也不会混淆二者用法 ✨

一、前言

在 Vue3 中,computed(计算属性) 和 watch(监听属性) 是处理响应式数据的两大核心 API,也是 Vue 开发中高频使用的基础知识点。二者都能监听响应式数据的变化,但设计初衷、使用场景、特性完全不同。

很多初学者容易混淆它们的用法,比如:什么时候用计算属性?什么时候用监听属性?为什么明明能用 computed 实现的功能,用 watch 会显得臃肿?

核心结论先记住:

  • 计算属性衍生新数据,用声明式的方式处理数据,做「数据加工」。
  • 监听属性监听数据变化,执行自定义的业务逻辑,做「事件处理」。

前置基础(必看)

在 Vue3 的 <script setup> 语法中,所有的组合式 API 都需要按需导入 后使用,这是 Vue3 的性能优化方案(按需引入,减少打包体积),computedwatch 也不例外,基础依赖导入如下:

javascript

运行

复制代码
import { ref, reactive, computed, watch, watchEffect } from 'vue'

补充:computed/watch 都是基于 refreactive 创建的响应式数据工作,这是学习本文的前置基础。


二、计算属性 computed 详解

2.1 核心概念

计算属性,本质是基于现有响应式数据,经过「计算 / 加工 / 处理」后,返回一个新的响应式数据 。简单理解:由其他数据派生出来的新数据,就是计算属性

2.2 核心特性(重中之重,面试高频)

✅ 特性 1:【响应式依赖】自动追踪依赖源

计算属性的返回值,依赖于声明的「源响应式数据」,当源数据发生变化时,计算属性的值会自动更新,页面也会同步刷新,完全不用手动干预。

✅ 特性 2:【缓存机制】性能优化核心(最核心)

这是计算属性最核心、最实用的特性,也是和「方法」最大的区别:

  • 只要计算属性依赖的源数据没有发生变化,无论多少次访问这个计算属性,都会直接返回「上一次计算的缓存结果」,不会重复执行计算逻辑。
  • 只有当依赖的源数据发生改变,计算属性才会重新执行内部的计算逻辑,生成新的值并缓存。
✅ 特性 3:【默认只读】简洁安全

计算属性默认是只读的,只能根据源数据计算出新值,不能直接修改计算属性本身的值,这种设计可以避免修改派生数据导致的逻辑混乱,保证数据流向的清晰。

✅ 特性 4:【声明式语法】更优雅

计算属性用「声明式」的写法处理数据逻辑,相比方法调用,代码更简洁、语义化更强,Vue 模板中使用时,和普通响应式数据的用法一致,不需要加括号调用

2.3 两种使用写法

✔ 写法 1:只读型计算属性(99% 的业务场景都用这个)

语法格式:传入一个回调函数,回调函数的返回值就是计算属性的值,默认只能读取,不能修改。

vue

复制代码
<template>
  <div>
    <p>num1:{{ num1 }}</p>
    <p>num2:{{ num2 }}</p>
    <p>求和结果(计算属性):{{ sum }}</p>
    <p>多次访问计算属性:{{ sum }} {{ sum }} {{ sum }}</p>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
// 源响应式数据
const num1 = ref(10)
const num2 = ref(20)

// 计算属性:求和(依赖 num1 和 num2)
const sum = computed(() => {
  console.log('计算属性的逻辑执行了')
  return num1.value + num2.value
})
</script>

运行结果

  1. 页面初始化时,控制台打印一次 计算属性的逻辑执行了,页面中多次访问 sum 都不会重复打印。
  2. 当修改 num1.valuenum2.value 时,控制台会再次打印,sum 的值也会自动更新。
✔ 写法 2:可读可写型计算属性(特殊场景使用)

默认的计算属性是只读的,但在一些特殊业务场景中,需要手动修改计算属性的值,此时可以给 computed 传入一个配置对象,对象中包含两个方法:

  • get():取值时执行的函数,逻辑和只读型计算属性一致,返回计算后的新值。
  • set(val):赋值时执行的函数,参数 val 是给计算属性赋的新值,在这个方法中可以手动修改「源响应式数据」。

核心原则:就算开启了可写,也不能直接修改计算属性 ,而是在 set 中修改「源数据」,由源数据再派生出计算属性的值。

vue

复制代码
<template>
  <div>
    <p>原始值:{{ num }}</p>
    <p>计算属性(翻倍):{{ doubleNum }}</p>
    <button @click="changeNum">修改计算属性的值为 50</button>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
const num = ref(10)

// 可读写的计算属性
const doubleNum = computed({
  // 读取时执行
  get() {
    return num.value * 2
  },
  // 修改时执行,val 是新赋值的值
  set(val) {
    num.value = val / 2
  }
})

// 手动修改计算属性的值
const changeNum = () => {
  doubleNum.value = 50
}
</script>

运行结果 :点击按钮后,doubleNum 被赋值为 50,触发 set 方法,num 的值被修改为 25,随后 get 方法执行,doubleNum 的值变成 50,页面同步刷新。

2.4 计算属性 vs 方法,为什么不用方法?

很多人会有疑问:既然计算属性能实现的功能,用方法也能实现(比如求和、格式化),为什么还要用计算属性?答案就是:缓存机制

举个例子,用方法实现上面的求和功能:

javascript

运行

复制代码
// 方法写法
const getSum = () => {
  console.log('方法执行了')
  return num1.value + num2.value
}

模板中调用:<p>{``{ getSum() }}</p>

核心区别

  • 方法 :每次页面渲染(包括组件重新渲染、其他数据变化),或者每次调用方法,都会重新执行方法的逻辑,没有缓存,性能开销大。
  • 计算属性:只有依赖的源数据变化,才会重新计算,否则永远走缓存,性能更高。

2.5 适用场景(记住一句话:无脑用 computed 的场景)

当需要对现有响应式数据做「格式化、运算、拼接、筛选、转换」,最终得到一个新的数据时,优先使用计算属性!

这是 computed 的黄金使用法则,常见业务场景举例:

  1. 数据运算:数字的加减乘除、取余、求和、平均值等。
  2. 数据格式化:时间戳转日期格式、数字转金额格式(如 1000 → ¥1000.00)、手机号脱敏等。
  3. 数据拼接:姓 + 名 = 完整姓名、省市区拼接成完整地址等。
  4. 列表筛选 / 过滤:根据条件过滤数组(如筛选出列表中的偶数、筛选出状态为已完成的任务)。
  5. 状态判断:根据多个变量判断一个状态(如 isDisabled = computed(() => !name.value || !phone.value))。

2.6 经典实战案例

案例:列表筛选(高频业务场景)

vue

复制代码
<template>
  <div>
    <p>原始数组:{{ list }}</p>
    <p>偶数列表(计算属性):{{ evenList }}</p>
    <p>大于5的数字列表(计算属性):{{ greaterFiveList }}</p>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
const list = ref([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

// 筛选出偶数列表
const evenList = computed(() => list.value.filter(item => item % 2 === 0))
// 筛选出大于5的数字列表
const greaterFiveList = computed(() => list.value.filter(item => item > 5))
</script>

三、监听属性 watch 详解

3.1 核心概念

监听属性,本质是监听一个或多个响应式数据的变化,当数据发生改变时,执行一段自定义的「业务逻辑代码」 。简单理解:你盯着某个数据,这个数据一旦变了,你就去做某件事

3.2 核心特性

✅ 特性 1:【监听数据变化】被动触发

watch 是「被动触发」的,只有当被监听的数据发生变化时,才会执行回调函数中的业务逻辑,没有数据变化则不会执行。

✅ 特性 2:【无返回值】专注业务逻辑

watch 没有返回值,它的核心作用不是生成新数据,而是处理业务逻辑,比如:发起网络请求、修改其他数据、操作 DOM、提示弹窗、路由跳转等。

✅ 特性 3:【精准监听】可指定监听目标

watch 可以精准监听单个数据、多个数据、对象的某个属性,监听目标明确,不会像计算属性那样自动追踪依赖,灵活性更高。

✅ 特性 4:【支持配置项】功能更强大

watch 提供了丰富的配置项,比如:立即执行、深度监听、取消监听等,可以满足各种复杂的业务场景。

3.3 基础使用写法(全场景覆盖,必学)

watch 的基础语法:接收三个参数,返回一个「停止监听」的函数

javascript

运行

复制代码
const stopWatch = watch(监听目标, 回调函数, 配置项)
✔ 写法 1:监听单个 ref 数据(最基础,最常用)

监听由 ref 声明的单个响应式数据,数据变化时,执行回调函数。回调函数的两个参数:

  • newVal:数据变化后的「新值」
  • oldVal:数据变化前的「旧值」

vue

复制代码
<template>
  <div>
    <p>当前数字:{{ num }}</p>
    <button @click="num++">点击加1</button>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'
const num = ref(0)

// 监听单个ref数据
watch(num, (newVal, oldVal) => {
  console.log('num发生了变化', '新值:', newVal, '旧值:', oldVal)
  // 这里可以写任意业务逻辑:比如请求接口、修改其他数据、弹窗提示等
  if (newVal >= 5) {
    alert('数字已经大于等于5了!')
  }
})
</script>
✔ 写法 2:监听多个响应式数据

如果需要同时监听多个数据的变化,只需要把「监听目标」写成一个数组即可,数组中的每一项都是要监听的响应式数据。回调函数的参数也是数组,顺序和监听目标一致,分别对应每个数据的新值和旧值。

javascript

运行

复制代码
import { ref, watch } from 'vue'
const num1 = ref(0)
const num2 = ref(0)

// 监听多个数据
watch([num1, num2], ([newNum1, newNum2], [oldNum1, oldNum2]) => {
  console.log('num1或num2发生了变化')
  console.log('num1:', newNum1, oldNum1)
  console.log('num2:', newNum2, oldNum2)
})
✔ 写法 3:监听 reactive 声明的对象 / 数组(深度监听)

对于由 reactive 声明的复杂数据类型(对象 / 数组)watch 会默认开启「深度监听」,无需手动配置,只要对象 / 数组内部的属性发生变化,就会触发回调。

注意:监听 reactive 对象时,回调函数的 oldVal 会和 newVal 指向同一个引用,无法获取真正的旧值,这是 Vue3 的已知特性。

vue

复制代码
<template>
  <div>
    <p>姓名:{{ user.name }}</p>
    <p>年龄:{{ user.age }}</p>
    <button @click="user.age++">修改年龄</button>
    <button @click="user.name = '李四'">修改姓名</button>
  </div>
</template>

<script setup>
import { reactive, watch } from 'vue'
const user = reactive({
  name: '张三',
  age: 20
})

// 监听reactive声明的对象
watch(user, (newVal, oldVal) => {
  console.log('user对象发生了变化', newVal)
  // 业务逻辑:比如保存用户信息到本地存储
  localStorage.setItem('user', JSON.stringify(newVal))
})
</script>
✔ 写法 4:监听对象的单个属性(精准监听)

很多时候,我们不需要监听整个对象,只需要监听对象的某个具体属性 ,此时可以把「监听目标」写成一个回调函数,返回要监听的属性值即可。

javascript

运行

复制代码
import { reactive, watch } from 'vue'
const user = reactive({
  name: '张三',
  age: 20
})

// 只监听user对象的age属性
watch(() => user.age, (newVal, oldVal) => {
  console.log('年龄发生了变化', newVal, oldVal)
})

3.4 常用配置项(3 个高频配置,必掌握)

watch 的第三个参数是一个配置对象,用来扩展监听的功能,常用的配置项只有 3 个,简单好记,覆盖 99% 的场景:

✅ 配置 1:immediate - 立即执行

默认情况下,watch 是「惰性执行」的:页面初始化时,不会执行回调函数,只有当监听的数据发生变化时才会执行。如果需要让 watch页面初始化时就立即执行一次 ,可以配置 immediate: true,这个配置在「页面加载时发起请求」的场景中非常常用。

javascript

运行

复制代码
watch(num, (newVal) => {
  console.log('立即执行+数据变化时执行', newVal)
}, { immediate: true })
✅ 配置 2:deep - 深度监听

当监听的是「ref 声明的复杂数据类型(对象 / 数组)」时,watch 默认不会监听内部属性的变化,此时需要手动配置 deep: true,开启深度监听。

补充:监听 reactive 声明的对象时,默认开启深度监听,无需手动配置。

javascript

运行

复制代码
import { ref, watch } from 'vue'
const user = ref({
  name: '张三',
  age: 20
})

// 监听ref声明的对象,需要开启深度监听
watch(user, (newVal) => {
  console.log('用户信息变化了', newVal)
}, { deep: true })
✅ 配置 3:取消监听

watch 会返回一个「停止监听」的函数,调用这个函数后,watch 将不再监听数据的变化,适合在「组件卸载」或「满足某个条件」时取消监听,避免内存泄漏。

javascript

运行

复制代码
import { ref, watch } from 'vue'
const num = ref(0)

// 接收停止监听的函数
const stopWatch = watch(num, (newVal) => {
  console.log('num变化了', newVal)
  if (newVal >= 5) {
    // 满足条件,取消监听
    stopWatch()
  }
})

3.5 适用场景(记住一句话:无脑用 watch 的场景)

当需要监听某个数据的变化,去执行一段「自定义的业务逻辑」时,优先使用 watch!

这是 watch 的黄金使用法则,watch 的核心是「处理逻辑」,不是「处理数据」,常见业务场景举例:

  1. 数据变化时,发起网络请求(比如:搜索框输入内容变化,发起搜索请求)。
  2. 数据变化时,修改其他相关数据(比如:用户选择的城市变化,更新对应的地区列表)。
  3. 数据变化时,执行弹窗提示、路由跳转、本地存储等操作。
  4. 监听路由参数的变化,做页面的刷新或数据的重新加载。
  5. 满足某个条件时,执行特定的业务逻辑(比如:数字大于 5 时弹窗提示)。

四、computed 和 watch 的核心区别(重中之重,必背)

这是本文的核心重点,也是面试中必考的高频问题 ,更是区分新手和老手的关键,一定要彻底理解并记住,二者的区别主要体现在以下 6 个维度,从核心到细节,一目了然:

✅ 核心区别 1:【设计初衷不同】------ 本质区别

  • computed :为了派生新数据 ,是「数据层面」的处理,它的产出是新的数据
  • watch :为了监听数据变化 ,是「行为层面」的处理,它的产出是执行业务逻辑

✅ 核心区别 2:【是否有返回值】

  • computed必须有返回值,回调函数的返回值就是计算属性的值,没有返回值则无意义。
  • watch没有返回值,回调函数中执行的是业务逻辑,不需要返回任何内容。

✅ 核心区别 3:【是否依赖缓存】

  • computed有缓存机制,依赖源不变则复用缓存,性能更高。
  • watch无缓存机制,只要监听的数据发生变化,就会执行回调函数,每次都是全新执行。

✅ 核心区别 4:【数据流向不同】

  • computed单向依赖,只能由「源数据」派生新数据,默认不能反向修改,数据流向清晰。
  • watch无固定流向,监听数据变化后,可以修改任意其他数据,灵活性更高。

✅ 核心区别 5:【触发方式不同】

  • computed自动触发,当依赖的源数据变化时,计算属性会自动更新,无需手动调用。
  • watch被动触发 ,只有监听的数据发生变化时,才会执行回调,也可以通过配置 immediate 主动触发。

✅ 核心区别 6:【使用场景不同】

  • computed :适合数据加工,凡是需要对现有数据做格式化、运算、筛选、拼接的场景,都用 computed。
  • watch :适合业务逻辑处理,凡是需要监听数据变化做请求、跳转、弹窗、修改其他数据的场景,都用 watch。

一句话总结区别(终极版)

能用 computed 实现的需求,一定不要用 watch;用 watch 实现的需求,computed 一定做不到。


五、补充:监听的语法糖 watchEffect

在 Vue3 中,还有一个和 watch 功能类似的 API ------ watchEffect,它是 watch 的「语法糖」,也叫「立即执行的监听」,这里做简单补充,方便大家在项目中灵活选择:

核心特点

  1. watchEffect 不需要指定监听目标,它会自动追踪回调函数中使用的所有响应式数据,只要其中任意一个数据变化,就会执行回调。
  2. watchEffect 是「立即执行」的,页面初始化时会执行一次,无需配置 immediate: true
  3. 写法更简洁,适合「多个数据变化都要执行相同逻辑」的场景。

简单示例

javascript

运行

复制代码
import { ref, watchEffect } from 'vue'
const num1 = ref(0)
const num2 = ref(0)

// 自动监听回调中用到的num1和num2
watchEffect(() => {
  console.log('num1或num2变化了', num1.value, num2.value)
})

watch vs watchEffect 怎么选?

  • 知道要「监听哪个具体数据」→ 用 watch,精准监听,性能更高。
  • 不知道要「监听哪些数据」,只知道「要执行什么逻辑」→ 用 watchEffect,写法简洁。

六、总结

核心知识点回顾

  1. 计算属性 computed派生新数据,有缓存,声明式,只读优先,适合做数据加工。
  2. 监听属性 watch监听数据变化,无缓存,命令式,执行逻辑,适合做业务处理。
  3. 二者都是 Vue3 处理响应式数据的核心 API,相辅相成,没有优劣之分,只有适用场景不同。

最佳实践(新手避坑指南)

  1. 不要滥用 watch:很多新手会用 watch 处理所有数据变化,比如用 watch 做求和、格式化,这是错误的,不仅代码臃肿,还浪费性能,这类需求一定要用 computed。
  2. 不要依赖 computed 做业务逻辑:计算属性的核心是返回新数据,不要在 computed 中执行请求、弹窗、修改其他数据等业务逻辑,这类需求一定要用 watch。
  3. 优先使用 computed :如果一个需求既可以用 computed 也可以用 watch 实现,优先用 computed,因为它更简洁、性能更高。

最后一句口诀(记牢终身受用)

数据变,出新数 → computed;数据变,做事情 → watch

希望这篇文章能帮助你彻底理解 Vue3 的 computed 和 watch,从此在开发中不再混淆,游刃有余 ✨。

相关推荐
六月June June2 小时前
leaflet L.popup().setContent中挂载vue组件
前端·javascript·vue.js
软件开发技术深度爱好者2 小时前
JavaScript的p5.js库使用详解(上)
开发语言·javascript
首席拯救HMI官2 小时前
【拯救HMI】HMI容错设计:如何减少操作失误并快速纠错?
大数据·运维·前端·javascript·网络·学习
深蓝电商API2 小时前
Scrapy与Splash结合爬取JavaScript渲染页面
javascript·爬虫·python·scrapy
m0_748254662 小时前
Vue.js 模板语法基础
前端·vue.js·flutter
donecoding2 小时前
AI时代程序员的护城河:让AI做创意组合,用标准化工具守住质量底线
javascript·架构·代码规范
PBitW2 小时前
和AI浅聊了一下SEO —— 真神Astro
前端·seo
胆大如牛白展堂2 小时前
自动刷新token登录
前端·设计模式
Charon_super2 小时前
html语法笔记
前端·笔记·html