《Vue3 中 computed、watch、watchEffect 怎么用?响应式核心能力详解》

一、写在前面

学完 refreactive 之后,很多新手会进入下一个典型阶段:

我已经会定义响应式数据了,也会改数据了,页面也能跟着更新。

但是很快又会碰到新的问题。

比如:

  • 我有姓名和年龄,能不能自动拼成一段介绍?

  • 我有商品单价和数量,能不能自动得到总价?

  • 输入框内容变化时,我想同步打印日志怎么办?

  • 某个状态变化后,我想发请求、保存本地数据、做一些副作用处理怎么办?

这时候你就会接触到 Vue3 响应式系统里另外三个非常重要的能力:

  • computed

  • watch

  • watchEffect

很多新手一开始看到这三个东西,会本能地觉得有点乱。

因为它们都和"数据变化"有关,但又不是一回事。

所以这一篇文章的目标,就是把它们之间的边界讲清楚。

你可以先记住一个非常核心的思路:

computed 更偏"算出一个值",watch 更偏"监听变化后执行动作",watchEffect 更偏"自动收集依赖并立即运行"。

后面我们就把这句话彻底拆开讲明白。


二、为什么会需要 computedwatch

先从最本质的问题开始。

你已经知道,Vue3 的核心是响应式。

也就是:

  • 数据变了

  • 页面会自动更新

但真实项目里,不是所有内容都直接来自"原始数据"。

很多时候,页面上显示的东西其实是"算出来的结果"。

比如:

  • 姓名 + 年龄 = 一段介绍文字

  • 商品价格 × 数量 = 总价

  • 列表过滤后 = 新列表

  • 是否登录 = 显示不同状态文案

还有一些场景,数据变化后你并不是想"显示一个新值",而是想"顺便做一件事"。

比如:

  • 输入框变化后发请求

  • 状态变化后保存到本地

  • 某个 id 变化后重新获取详情

  • 页面参数变化后打印日志

所以你会发现,围绕"数据变化",其实至少有两类需求:

第一类:我想得到一个新的结果值

这种通常更适合 computed

第二类:我想在数据变化后执行额外动作

这种通常更适合 watch

watchEffect,则是另一种更自动化的监听方式。


三、computed 到底是什么?

先给一个最直接的定义:

computed 是计算属性,用来根据已有响应式数据,计算出一个新的值。

注意这里的关键词是:

  • 根据已有数据

  • 计算出新值

也就是说,computed 更像是:

由别的数据推导出来的结果。

它不是拿来随便执行一段逻辑的,而是更偏向"得出一个可直接使用的值"。


四、一个最简单的 computed 例子

复制代码
<script setup>
import { ref, computed } from 'vue'

const firstName = ref('张')
const lastName = ref('三')

const fullName = computed(() => {
  return firstName.value + lastName.value
})
</script>

<template>
  <div>
    <p>姓:{{ firstName }}</p>
    <p>名:{{ lastName }}</p>
    <p>姓名:{{ fullName }}</p>
  </div>
</template>

这里发生了什么?

  • firstName 是原始数据

  • lastName 是原始数据

  • fullName 不是手动写死的,而是算出来的

也就是说:

fullName 是一个"派生值"。

只要 firstNamelastName 变化,fullName 就会自动跟着更新。

这就是计算属性最经典的使用场景。


五、为什么不用普通函数,而要用 computed

这是非常关键的问题。

很多新手看到上面的代码,第一反应会是:

"这不就是个函数吗?我直接写个函数返回拼接结果不就行了?"

比如:

复制代码
<script setup>
import { ref } from 'vue'

const firstName = ref('张')
const lastName = ref('三')

const getFullName = () => {
  return firstName.value + lastName.value
}
</script>

<template>
  <p>{{ getFullName() }}</p>
</template>

这当然也能工作。

computed 和普通函数,核心区别不在"能不能得到结果",而在于:

computed 会基于依赖做缓存,而普通函数每次渲染都会重新执行。


六、什么叫"缓存"?

可以先这样理解:

如果 computed 依赖的数据没有变化,那么它不会反复重新计算。

它会直接复用上一次的结果。

例如:

复制代码
const fullName = computed(() => {
  console.log('我重新计算了')
  return firstName.value + lastName.value
})

如果 firstNamelastName 没变,那么模板多次读取 fullName 时,并不会一直重新跑这段逻辑。

这就是 computed 的价值之一:

它适合做"基于响应式数据推导出的值",而且有缓存。

所以你可以这样理解:

  • 普通函数:调用一次算一次

  • computed:依赖没变,就尽量复用已有结果

对于复杂计算来说,这会更高效,也更符合 Vue 的响应式思路。


七、computed 最适合哪些场景?

你可以把计算属性理解成"展示层的派生结果"。

最常见的场景包括:

1. 拼接展示内容

复制代码
const fullName = computed(() => {
  return firstName.value + lastName.value
})

2. 计算总价

复制代码
const totalPrice = computed(() => {
  return price.value * count.value
})

3. 计算过滤后的列表

复制代码
const doneList = computed(() => {
  return list.value.filter(item => item.done)
})

4. 根据状态返回文案

复制代码
const statusText = computed(() => {
  return isLogin.value ? '已登录' : '未登录'
})

你会发现它们有一个共同点:

都不是"额外执行动作",而是在"产出一个新值"。

这就是判断能不能用 computed 的关键标准。


八、computed 的写法怎么理解?

最常见写法是:

复制代码
const 变量名 = computed(() => {
  return 某个计算结果
})

比如:

复制代码
const doubleCount = computed(() => {
  return count.value * 2
})

它的语义其实非常直白:

  • 我定义了一个叫 doubleCount 的计算属性

  • 它的值由 count 推导而来

  • count 变了,它就重新计算

你可以把它理解成"响应式版本的自动计算结果"。


九、watch 又是什么?

如果说 computed 是"根据已有数据算出一个新值",

那么 watch 更像是:

我专门盯着某个数据,一旦它变了,就执行一段逻辑。

注意关键词是:

  • 盯着某个数据

  • 变了之后执行逻辑

这和 computed 的方向明显不同。

computed 更像"我要一个结果"。
watch 更像"我要在变化发生后做事"。


十、一个最简单的 watch 例子

复制代码
<script setup>
import { ref, watch } from 'vue'

const count = ref(0)

watch(count, (newValue, oldValue) => {
  console.log('新值:', newValue)
  console.log('旧值:', oldValue)
})
</script>

<template>
  <div>
    <p>{{ count }}</p>
    <button @click="count++">加一</button>
  </div>
</template>

这里的意思是:

  • watch 在监听 count

  • 只要 count 变化

  • 回调函数就会执行

  • 并且你还能拿到新值和旧值

所以 watch 的基本思路是:

监听谁,谁变了,我就触发一个回调。


十一、watch 更适合做什么?

这一点非常关键。

watch 一般不拿来"专门算一个值",

而更适合做"副作用逻辑"。

什么叫副作用?

你可以先简单理解成:

不是为了直接返回一个值,而是为了在变化后额外执行某些动作。

例如:

1. 打印日志

复制代码
watch(count, (newValue) => {
  console.log('count 变成了:', newValue)
})

2. 保存到本地存储

复制代码
watch(username, (newValue) => {
  localStorage.setItem('username', newValue)
})

3. 参数变化后发请求

复制代码
watch(userId, async (newId) => {
  // 根据新 id 获取用户详情
})

4. 表单变化后做校验

复制代码
watch(email, (newValue) => {
  // 做邮箱格式检查
})

你会发现,这些都不是在"生成一个展示值",

而是在"变化发生后执行逻辑"。

所以:

  • 要"算值",优先想 computed

  • 要"监听变化做事",优先想 watch


十二、watch 的回调参数怎么理解?

看这个例子:

复制代码
watch(count, (newValue, oldValue) => {
  console.log(newValue, oldValue)
})

这里:

  • newValue 是变化后的新值

  • oldValue 是变化前的旧值

例如 count1 变成 2,那么:

  • newValue = 2

  • oldValue = 1

这在调试和业务处理中很有用,因为你不仅知道"变了",还知道"怎么变的"。


十三、watch 能监听多个数据吗?

可以。

例如:

复制代码
<script setup>
import { ref, watch } from 'vue'

const firstName = ref('张')
const lastName = ref('三')

watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
  console.log('新值:', newFirst, newLast)
  console.log('旧值:', oldFirst, oldLast)
})
</script>

这表示:

  • 同时监听 firstNamelastName

  • 只要它们其中一个变了,就会触发回调

这在多条件联动时很有用。


十四、watch 监听对象时要注意什么?

如果你监听的是对象,会稍微复杂一点。

例如:

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

如果你直接写:

复制代码
watch(user, (newValue, oldValue) => {
  console.log('user 变了')
})

这是可以工作的,因为 reactive 对象本身就是响应式对象。

但如果你监听的是对象里的某一个属性,更推荐这样写:

复制代码
watch(() => user.name, (newValue, oldValue) => {
  console.log('姓名变化了')
})

这里的写法很重要:

当你想监听某个属性时,通常要传一个函数过去。

也就是:

复制代码
() => user.name

这样 Vue 才知道你到底在监听什么。


十五、watchEffect 又是什么?

现在到了很多新手更容易混乱的地方。

先给定义:

watchEffect 会立即执行一次回调,并自动收集回调中用到的响应式依赖;这些依赖变化后,它会重新执行。

这个定义看起来有点绕,我们拆开讲。


1. 它会先立即执行一次

例如:

复制代码
<script setup>
import { ref, watchEffect } from 'vue'

const count = ref(0)

watchEffect(() => {
  console.log('当前 count 是:', count.value)
})
</script>

这段代码一运行,回调会立刻执行一次。

哪怕你还没有点击任何按钮。


2. 它会自动收集依赖

在这个例子里,回调里用到了:

复制代码
count.value

所以 Vue 会自动知道:

  • 这个 watchEffect 依赖了 count

以后只要 count 变化,回调就会再次执行。


3. 它和 watch 最大的区别是什么?

你可以先这样记:

watch

你要明确告诉它"监听谁"

复制代码
watch(count, () => {
  // ...
})
watchEffect

你不用手动指定"监听谁",它会根据回调里实际用到的响应式数据自动收集依赖

复制代码
watchEffect(() => {
  console.log(count.value)
})

所以:

watch 是显式监听,watchEffect 是自动收集依赖。


十六、watchEffect 更适合什么场景?

它比较适合这种情况:

我不太想手动列出监听源,我只是想写一段依赖响应式数据的逻辑,让它自动跟着跑。

比如:

1. 自动打印某些状态

复制代码
watchEffect(() => {
  console.log('当前用户名:', username.value)
  console.log('当前年龄:', age.value)
})

这里它会自动依赖 usernameage

2. 初始化和后续变化都要执行同一段逻辑

比如:

  • 页面一进来就执行一次

  • 后面依赖变了也继续执行

这种"立即执行 + 自动依赖追踪"的模式,很适合 watchEffect


十七、computedwatchwatchEffect 三者到底怎么区分?

这是这一篇最关键的总结部分。

我们直接从使用意图来区分。


1. computed

当你想:

根据已有响应式数据,得到一个新的结果值

就优先考虑 computed

比如:

  • 全名

  • 总价

  • 已完成任务数量

  • 过滤后的列表

  • 状态文案

一句话理解:

它是"算结果"的。


2. watch

当你想:

明确监听某个或某些数据,一旦变化就执行动作

就优先考虑 watch

比如:

  • 数据变化后发请求

  • 状态变化后存本地

  • 监听路由参数变化

  • 表单项变化后校验

一句话理解:

它是"盯变化做事"的。


3. watchEffect

当你想:

写一段依赖响应式数据的逻辑,让它先执行一次,以后依赖变了自动再执行

就可以考虑 watchEffect

一句话理解:

它是"自动依赖收集 + 立即执行"的监听方式。


十八、一个综合例子:三者放在一起看

下面给你一个综合示例,把三者放到同一个组件里理解。

复制代码
<script setup>
import { ref, computed, watch, watchEffect } from 'vue'

const price = ref(100)
const count = ref(2)

const totalPrice = computed(() => {
  return price.value * count.value
})

watch(count, (newValue, oldValue) => {
  console.log(`数量从 ${oldValue} 变成了 ${newValue}`)
})

watchEffect(() => {
  console.log(`当前单价:${price.value},当前数量:${count.value}`)
})
</script>

<template>
  <div>
    <p>单价:{{ price }}</p>
    <p>数量:{{ count }}</p>
    <p>总价:{{ totalPrice }}</p>

    <button @click="count++">数量加一</button>
  </div>
</template>

这里三者的分工非常清晰:

  • totalPrice:负责算结果,用 computed

  • watch(count, ...):明确监听数量变化,用 watch

  • watchEffect(...):自动读取当前依赖,并立即执行,用 watchEffect

这个例子如果你看顺了,这一篇的主线就差不多通了。


十九、新手最常见的几个误区

这一部分非常重要,因为很多人就是在这里开始"似懂非懂"。


1. 把 computed 当成"普通函数替代品"

computed 不是为了单纯省几行函数代码,

它的重点是:

  • 它表示一个"派生值"

  • 它有响应式依赖

  • 它有缓存

所以不要把它简单理解成"另一种函数"。


2. 用 watch 去计算展示值

例如你为了得到总价,写成:

复制代码
watch([price, count], () => {
  total.value = price.value * count.value
})

虽然不是绝对不能做,但这个场景更自然的写法通常是 computed

因为总价本质上就是"由别的数据推导出的结果"。

所以:

能直接算出结果的,优先想 computed


3. 看到监听就无脑用 watchEffect

watchEffect 虽然方便,但不是所有监听都该用它。

如果你想精准监听某个明确数据,并且想区分新旧值,

通常 watch 更合适。

因为 watchEffect

  • 自动收集依赖

  • 没有显式监听源那么直观

  • 某些情况下不如 watch 可控

所以前期不要把它当万能工具。


4. 分不清"值"和"动作"

你可以用一个特别实用的判断法:

如果你需要的是"一个值"

比如总价、全名、过滤结果

优先考虑 computed

如果你需要的是"执行一个动作"

比如请求、日志、存储、校验

优先考虑 watch / watchEffect

这个判断法特别有用。


二十、这一篇学完后,你应该达到什么程度?

如果你把这篇真正理解了,至少应该做到:

  • 知道 computed 是计算属性

  • 知道 computed 适合产出派生值

  • 知道它和普通函数的重要区别是缓存

  • 知道 watch 是显式监听数据变化

  • 知道 watch 更适合做副作用逻辑

  • 知道 watchEffect 会立即执行并自动收集依赖

  • 能大致判断什么时候用 computed,什么时候用 watch

  • 看到这三者的代码时,不再觉得它们像一团东西

只要这一层真正清楚了,你对 Vue3 响应式系统的掌握就已经从"会改数据"进入到"会组织响应式逻辑"了。


二十一、总结

这一篇文章,我们把 Vue3 响应式系统里的三个核心能力串起来讲清楚了:

  • computed:根据已有响应式数据,计算出新的结果值

  • watch:明确监听某个数据的变化,并在变化后执行逻辑

  • watchEffect:自动收集依赖,立即执行,并在依赖变化后重新执行

最重要的不是死记定义,而是建立下面这个判断意识:

  • 要结果,用 computed

  • 要监听变化做事,用 watch

  • 要自动依赖收集并立即执行,可以考虑 watchEffect

如果说上一篇让你理解了 Vue3 的"响应式状态"是怎么定义的,

那么这一篇,就是让你开始真正掌握:

响应式状态变化之后,值怎么推导、逻辑怎么联动。

这会直接影响你后面写表单、写组件通信、写项目状态管理时的思路。

相关推荐
ego.iblacat2 小时前
在 LNMP 平台中部署 Web 应用
android·前端·adb
浩宇软件开发2 小时前
springBoot+Vue中华诗词学习后台管理系统
vue.js·spring boot·axios·element-plus·router
weixin199701080162 小时前
南网商城商品详情页前端性能优化实战
java·前端·性能优化
陈天伟教授2 小时前
WEB应用安全与防护 - 实操案例 2:CSRF(跨站请求伪造)—— 伪造用户操作
前端·安全·xss
@PHARAOH2 小时前
HOW - 依赖包版本 lock 维护策略(pnpm)
前端
SuperEugene2 小时前
前端-后端-产品-项目-运维:互联网项目协作全流程解析
运维·前端·javascript
weixin199701080162 小时前
网易考拉商品详情页前端性能优化实战
java·前端·python·性能优化
A黄俊辉A2 小时前
openlayers+vue初学注意点
前端·javascript·vue.js
南篱2 小时前
从回调地狱到优雅异步:JavaScript 异步编程的完整演进之路
前端·javascript·面试