前端开发··2 阅读·预计 5 分钟

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 评论

评论区

登录 后参与评论