computed 与 watch
约 691 字大约 2 分钟
布欧-Lewyon
2026-05-15
首页 › Vue › Composition API › computed 与 watch
computed
只读计算属性
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
// 只读 computed
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
</script>
<template>
<p>{{ fullName }}</p>
</template>可写计算属性
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(val) {
const parts = val.split(' ')
firstName.value = parts[0]
lastName.value = parts[1] || ''
}
})
function change() {
fullName.value = 'Alice Wang' // → firstName='Alice', lastName='Wang'
}
</script>computed 调试
<script setup>
const count = ref(0)
const double = computed(() => count.value * 2, {
onTrack(e) { console.log('追踪:', e) }, // 开发环境
onTrigger(e) { console.log('触发:', e) }
})
</script>watch
监听 ref
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
const name = ref('Alice')
// 监听单个 ref
watch(count, (newVal, oldVal) => {
console.log(`count: ${oldVal} → ${newVal}`)
})
// 监听多个 ref
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
console.log('count 或 name 变化了')
})
</script>监听 reactive
<script setup>
import { reactive, watch } from 'vue'
const state = reactive({ count: 0, nested: { value: 1 } })
// 监听整个 reactive → 默认 deep: true
watch(state, (newVal) => {
console.log('state 变化', newVal)
})
// 监听 reactive 的某个属性 → 需要 getter 函数
watch(
() => state.count,
(val) => { console.log('count:', val) }
)
// 监听嵌套属性
watch(
() => state.nested.value,
(val) => { console.log('nested.value:', val) }
)
</script>watch 选项
<script setup>
import { ref, watch } from 'vue'
const query = ref('')
const user = ref({ name: 'Alice', age: 25 })
// immediate:立即执行一次
watch(query, (val) => {
fetchResults(val)
}, { immediate: true })
// deep:深度监听
watch(user, (val) => {
console.log('user 变化', val)
}, { deep: true })
// flush:控制回调时机
watch(source, callback, {
flush: 'post' // DOM 更新后执行(默认)
// flush: 'pre' // DOM 更新前执行
// flush: 'sync' // 值变化时同步执行
})
// once:只触发一次(Vue 3.4+)
watch(source, callback, { once: true })
</script>watchEffect
watchEffect 自动收集依赖,无需指定监听源:
<script setup>
import { ref, watchEffect } from 'vue'
const count = ref(0)
const name = ref('Alice')
watchEffect(() => {
// 会自动追踪 count 和 name 的读取
console.log(`count=${count.value}, name=${name.value}`)
})
// 修改 → watchEffect 自动重新执行
count.value++ // 触发
name.value = 'Bob' // 触发watch vs watchEffect
| 对比 | watch | watchEffect |
|---|---|---|
| 源 | 需显式指定 | 自动追踪内部读取的响应式数据 |
| 旧值 | ✅ 有 | ❌ 无 |
| 立即执行 | 默认 immediate: false | ✅ 默认立即执行 |
| 副作用清理 | onCleanup | onCleanup |
| 调试 | ✅ watch | flush: 'sync' + onTrack |
副作用清理
异步场景中防止竞态:
watch(id, async (newId, oldId, onCleanup) => {
let cancelled = false
onCleanup(() => { cancelled = true }) // 下次执行时清理上次
const data = await fetch(`/api/${newId}`)
if (!cancelled) {
result.value = data
}
})
// watchEffect 同样支持
watchEffect(async (onCleanup) => {
let cancelled = false
onCleanup(() => { cancelled = true })
// ...
})停止监听
watch / watchEffect 返回停止函数:
<script setup>
import { ref, watchEffect } from 'vue'
const count = ref(0)
const stop = watchEffect(() => {
console.log(count.value)
})
// 5 秒后停止监听
setTimeout(() => stop(), 5000)
</script>小结
| API | 何时用 |
|---|---|
computed | 从已有数据派生新值(自动缓存) |
watch | 数据变化时执行异步/副作用操作 |
watchEffect | 不需要旧值,自动追踪依赖 |
上一节:setup 与 ref / reactive 下一节:生命周期与模板 ref
