外观
Vue 计算属性与侦听器
- 状态:未准备
- 适用方向:前端 / Go 全栈
- 最近复习:待填写
- 下次复习:待安排
一句话回答
computed 用于缓存同步派生值,watch 显式监听来源并执行副作用,watchEffect 自动追踪同步执行阶段读取的依赖;异步请求必须处理失效与取消。
原理解释
| 方式 | 依赖与执行 | 适用场景 |
|---|---|---|
| computed | 自动追踪 getter 读取,按需求值并缓存 | 筛选、汇总、展示值 |
| watch | 显式指定 ref、getter、响应式对象或来源数组,默认惰性 | 请求、持久化、对比新旧值 |
| watchEffect | 立即执行,自动追踪同步读取 | 多个关联依赖驱动的外部同步 |
computed getter 应保持纯净,不发请求或修改其他状态;普通方法每次调用都会执行,computed 可以复用依赖未变时的结果。没有响应式依赖的 Date.now 不会因为时间流逝自动让计算属性重算。
ts
import { ref, computed, watch } from 'vue'
const keyword = ref('')
const items = ref([{ id: 1, title: 'Vue' }])
const visible = computed(() =>
items.value.filter(item => item.title.includes(keyword.value)),
)
const result = ref<unknown>(null)
const error = ref<string | null>(null)
watch(keyword, async (query, _old, onCleanup) => {
const controller = new AbortController()
let active = true
onCleanup(() => { active = false; controller.abort() })
error.value = null
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
signal: controller.signal,
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
const data: unknown = await response.json()
if (active) result.value = data
} catch (cause) {
if (active) error.value = String(cause)
}
}, { immediate: true })清理在本次 watcher 失效时执行;先注册清理,再等待请求。active 阻止旧结果写回,abort 尽量取消工作。实际业务还需 loading、数据校验和缓存策略。
场景与边界
watch(() => state.count, ...)监听属性;直接传state.count得到的是普通值,不是有效响应式来源。- 直接 watch reactive 对象会隐式深层监听;watch 返回对象的 getter 通常需 deep 才追踪嵌套修改。深层原地变更时 newValue/oldValue 可能是同一对象,不会自动生成历史快照。
- watchEffect 的异步回调只追踪首次 await 前同步访问的依赖,await 后首次读取的值不会因此被收集。
- 默认 watcher 回调在自身组件 DOM 更新前执行;读取更新后的 DOM 可用
flush:'post'。flush:'sync'不批处理,应谨慎用于高频变更。 - Vue 3.5+ 的 onWatcherCleanup 必须同步注册,不能在 await 后调用;示例使用回调参数 onCleanup,兼容更广的 Vue 3 版本。
项目中的应用
目前没有 Vue 源码证据。可对照 请求竞态与实时通信 将搜索竞态写成 watch 版本,控制返回顺序验证旧结果不会覆盖新查询。
常见追问
- watch 能代替 computed 吗? 技术上可维护另一份状态,但会增加同步分支;纯派生值优先 computed。
- deep 一定更好吗? 深遍历有成本,应明确监听字段;Vue 3.5+ 还支持数值深度上限,需核对目标版本。
- immediate 与 watchEffect 等价吗? 不等价,前者仍使用显式来源,不把回调中的所有读取当来源。