Vue 3 computed 和 watch 使用场景划分

FreeGuideOnline 最新 2026-07-05

javascript const firstName = ref('John') const lastName = ref('Doe')

const fullName = computed(() => ${firstName.value} ${lastName.value})

> 任意一个依赖改变,`fullName` 自动更新。

#### 2. 对列表进行过滤、排序、搜索
```javascript
const todos = ref([...])
const hideCompleted = ref(false)

const filteredTodos = computed(() => {
  if (hideCompleted.value) {
    return todos.value.filter(todo => !todo.completed)
  }
  return todos.value
})

不需要手动监听 todoshideCompletedfilteredTodos 自动保持为最新状态。

3. 组合多个状态生成布尔标志或计算值

const items = ref([...])
const hasItems = computed(() => items.value.length > 0)
const totalPrice = computed(() =>
  items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
)

4. 数据格式的统一转换

const rawDate = ref('2025-03-28')

const formattedDate = computed(() => {
  return new Date(rawDate.value).toLocaleDateString('zh-CN')
})

关键原则

  • 如果一段数据完全由其他数据计算得来,且不需要手动控制更新时机,就应该使用 computed
  • 不要在 computed 中执行异步操作、修改其他状态或发送请求。

watch:监听数据变化并执行副作用

watch 用于在特定数据变化时执行有副作用的操作,比如请求数据、操作 DOM、写入本地存储等。

核心特性

  • 显式声明要监听的源:可以监听单个 refreactive 对象的属性、计算属性或是一个返回值的函数。
  • 回调参数提供新旧值:可以拿到变化前后的值,方便比较。
  • 支持异步操作:回调可以是 async 函数,内部可以执行任何操作。
  • 深度监听与立即执行:通过 { deep: true, immediate: true } 控制监听行为。

典型适用场景

1. 依据某个数据变化发送网络请求

const userId = ref(1)
const userInfo = ref(null)

watch(userId, async (newId) => {
  userInfo.value = await fetch(`/api/users/${newId}`).then(r => r.json())
})

computed 无法处理异步请求,这里必须使用 watch

2. 数据变化时同步到外部(如 localStorge、Cookie)

const cart = ref(loadCartFromStorage())

watch(cart, (newCart) => {
  localStorage.setItem('cart', JSON.stringify(newCart))
}, { deep: true })

3. 监听多个数据源并执行统一副作用

const keyword = ref('')
const page = ref(1)

watch([keyword, page], ([newKeyword, newPage]) => {
  fetchSearchResults(newKeyword, newPage)
})

4. 组件 props 变化时需要执行操作

const props = defineProps({
  modelValue: Boolean
})

watch(() => props.modelValue, (newVal) => {
  if (!newVal) {
    resetForm()
  }
})

5. 需要访问 DOM 更新之后的回调(结合 flush: 'post'

const listRef = ref(null)
const items = ref([])

watch(items, () => {
  console.log('列表长度', listRef.value?.children.length)
}, { flush: 'post' })

何时避免使用 watch

  • 当某个值只是单纯地由其他数据计算而来,没有任何副作用时,用 computed 更清晰。
  • 多个 watch 分别监听不同依赖去更新同一个衍生状态,往往是可以用计算属性替代的坏味道。

场景划分决策指南

维度 computed watch
目的 派生出新的状态 响应数据变化执行副作用
关注点 结果是什么 当什么发生时我要做什么
返回值 必须返回一个值,并作为响应式引用 无返回值(或无需关心返回值)
异步支持 不支持,函数必须是纯同步的 完全支持,回调可以是 async
依赖追踪 自动追踪依赖 显式指定要监听的源
缓存 有缓存,依赖不变不重新计算 无缓存,每次触发都会执行回调
典型代码 const x = computed(() => a + b) watch(a, (newVal) => { ... })

快速决策流程

  1. 你需要得到一个新值供模板或逻辑使用吗?
    → 是,且该值完全由其他响应式数据算出来 → 用 computed
  2. 数据变化时你需要执行动作(请求、存储、手动修改其它状态等)?
    → 是 → 用 watch
  3. 你既需要新值,又要执行副作用?
    → 将计算部分抽离为 computed,副作用部分单独用 watch 该计算属性或源数据。

常见误区与最佳实践

误区1:用 watch 来组装衍生数据

// ❌ 不好的做法
const fullName = ref('')
watch([firstName, lastName], ([first, last]) => {
  fullName.value = `${first} ${last}`
})

直接用 computed 替换:

const fullName = computed(() => `${firstName.value} ${lastName.value}`)

误区2:在 computed 中执行副作用

// ❌ 错误:计算属性中发送请求
const user = computed(async () => {
  return await fetchUser(id.value)
})

应该用 watchwatchEffect

误区3:过度使用 deep: true

深度监听会递归遍历所有嵌套属性,开销较大。
如果只需要监听某个深层属性的变化,可以精确监听:

watch(() => obj.value.nested.key, callback)

而不是:

watch(obj, callback, { deep: true })