BLOG
Article
computed、watch、watchEffect 的选择,不是 API 记忆题
computed、watch、watchEffect 都能“跟着响应式数据变化”,所以很容易被当成三个可以互换的 API。
但它们的设计目标不同:
computed计算派生值。watch显式侦听数据源,执行副作用。watchEffect自动追踪依赖,立即执行副作用。
先想清楚你要的是值还是副作用,选择会自然很多。
computed:从状态派生值
const firstName = ref("Ada")
const lastName = ref("Lovelace")
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
fullName 是一个只读 ref,依赖不变时会使用缓存。
适合:
- 过滤、排序、格式化
- 多个状态组合出一个新值
- 在模板中重复使用派生值
不适合:
- 发请求
- 写 DOM
- 修改其他 state
- 依赖异步结果后再修改外部状态
computed 的 getter 应该是纯计算,不要在里面制造副作用。
watch:显式侦听
watch(
() => props.userId,
async (userId, oldUserId, onCleanup) => {
const controller = new AbortController()
onCleanup(() => controller.abort())
const user = await fetchUser(userId, {
signal: controller.signal,
})
data.value = user
},
{ immediate: true },
)
watch 默认是懒执行的,只有依赖变化才触发。它提供新值、旧值,适合做:
- 请求
- 本地存储同步
- 路由参数变化后的副作用
- 需要比较新旧值的逻辑
常用配置:
immediate: true:创建时立即执行一次deep: true:深度侦听对象flush: "post":在 DOM 更新后执行flush: "sync":依赖变化时同步执行,通常只在特殊场景使用
deep 不是越多越好。深层侦听大对象会带来遍历和触发成本,优先侦听具体字段。
watchEffect:自动追踪
const stop = watchEffect(() => {
console.log(userId.value)
})
watchEffect 会立即执行一次,并自动追踪回调里访问到的响应式依赖。
它适合依赖关系简单、逻辑集中、不需要旧值的副作用。
缺点是依赖是隐式的:
watchEffect(async () => {
const user = await fetchUser(userId.value)
data.value = user
})
看起来没问题,但如果 userId 快速变化,旧请求可能后返回,覆盖新请求。watchEffect 不会自动解决竞态。
需要取消时:
watchEffect(onCleanup => {
const controller = new AbortController()
onCleanup(() => controller.abort())
fetch(`/api/users/${userId.value}`, {
signal: controller.signal,
})
})
watch 也可以使用 onCleanup,在回调参数里注册清理函数。
flush 的时机
默认 flush: "pre",侦听回调在组件更新前执行。
如果需要在 DOM 更新后读取布局:
watch(
() => props.open,
() => {
nextTick(() => {
// 读取更新后的 DOM
})
},
{ flush: "post" },
)
flush: "sync" 会同步触发,可能造成多次执行和性能问题。除非你非常清楚依赖更新顺序,否则不要把它当默认选项。
怎么选
要一个派生值 -> computed
要显式侦听并在变化时做副作用 -> watch
依赖简单、需要自动追踪 -> watchEffect
如果 computed 里出现了请求或 DOM 操作,说明你想做的是副作用。
如果 watchEffect 里依赖很多、分支复杂,watch 的显式数据源通常更容易读。
如果多个状态变化都需要触发同一个异步请求,要考虑取消和去重,而不是只选一个 API。
最后
这三个 API 的差别不在“谁更高级”,而在职责:
computed负责算。watch负责看。watchEffect负责自动跟着跑。
把职责分清,再处理异步清理和竞态,响应式代码会稳定很多。