Vue 中 watch 与 computed 的使用边界
watch 和 computed 都能响应数据变化,但一个用于派生值,一个用于副作用。真正的问题不在 API 名字,而在于边界一旦混用,代码很快就会失控。下面直接用代码说明。
引言
很多 Vue 项目越写越乱,往往不是因为能力不够,而是因为所有逻辑都塞进了 watch。这个问题不需要长篇空谈,直接看边界和代码示例更有效。
先判断:这是派生值,还是副作用
能纯计算出来的,用 computed;需要请求、写缓存、同步路由的,用 watch。这个边界清楚了,后面的选择就简单很多。
派生值用 computed
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
const visibleTodos = computed(() => {
return todos.value.filter(todo => !todo.done);
});
副作用用 watch
watch(keyword, async (value) => {
loading.value = true;
try {
result.value = await fetchList(value);
} finally {
loading.value = false;
}
});
最常见的错误:用 watch 维护派生状态
这类代码短期能跑,长期会越来越难维护,因为你把本来一条公式能表达的关系,改写成了命令式同步逻辑。
反例
const fullName = ref('');
watch([firstName, lastName], () => {
fullName.value = `${firstName.value} ${lastName.value}`;
});
正例
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});
带异步和外部交互时,watch 才是主场
只要逻辑里出现请求、节流、防抖、URL 同步,这就不是 computed 的事。
搜索请求 + 防抖
const debouncedFetch = useDebounceFn(async (value: string) => {
list.value = await fetchSearch(value);
}, 300);
watch(keyword, (value) => {
if (!value.trim()) {
list.value = [];
return;
}
debouncedFetch(value);
});
同步 URL 参数
watch(currentTab, (tab) => {
router.replace({
query: { ...route.query, tab }
});
});
推荐的组织方式
展示用派生值尽量收进 computed,外部交互动作收进 watch 或方法里。回头看代码时,边界会非常清楚。
推荐结构
const keyword = ref('');
const sourceList = ref<Todo[]>([]);
const filteredList = computed(() => {
return sourceList.value.filter(item =>
item.title.includes(keyword.value)
);
});
watch(keyword, async (value) => {
if (!value.trim()) return;
sourceList.value = await fetchTodos(value);
});
总结
- computed:算值
- watch:做副作用
- 不要用 watch 维护派生状态
- 异步请求、缓存同步、路由联动优先放 watch
- 代码结构比 API 选择本身更重要
0 评论
评论区
登录 后参与评论