别瞎用 Composition API:90% 的开发者都在写“面条代码”

38 阅读5分钟

引言

Vue 3 普及这么久了,我发现一个奇怪的现象:大家的组件并没有变得更整洁,反而变得更乱了。

以前在 Options API 时代,虽然逻辑是跳跃的(data 在上面,methods 在下面),但至少还有个格子供你填空。现在到了 Composition API 时代,script setup 给了一块自由的画布,结果 90% 的开发者把这里当成了新的“垃圾回收站”

打开你的项目看一眼,是不是所有的变量都堆在文件顶部,所有的函数都堆在底部?如果一个组件有 500 行,你要修一个 bug,是不是还得在第 5 行定义的变量和第 300 行使用的函数之间来回滚动?

这不叫 Composition API,这叫 "Options API in setup"

核心问题拆解:为什么你写出了“面条代码”?

很多从 Vue 2 转过来的同学都有一个误区:认为 Composition API 的目的是为了“不用 this”或者“更好地支持 Typescript”。

这只是表象。Composition API 的真正核心在于 关注点分离 (Separation of Concerns)

在 Options API 中,代码是按技术类型组织的(所有的 data 放一起,所有的 computed 放一起)。 在 Composition API 中,代码应该是按逻辑功能组织的(搜索功能相关的 data、methods、computed 放在一起)。

如果你只是把 data 变成了 ref,把 methods 变成了 function,然后依然把它们散落在这一整块 setup 里,那你实际上是在写 "面条代码" (Spaghetti Code) —— 逻辑纠缠不清,难以维护。

Bad Case 展示:

// 你现在的代码是不是长这样?
<script setup>
import { ref, onMounted, watch } from 'vue'

// --- 搜索功能的变量 ---
const keyword = ref('')
const searchResult = ref([])

// --- 分页功能的变量 ---
// (你看,这里已经开始乱了)
const currentPage = ref(1)
const pageSize = ref(10)

// --- 弹窗功能的变量 ---
const showModal = ref(false)

// --- 搜索功能的方法 ---
const handleSearch = () => { ... }

// --- 分页功能的方法 ---
const changePage = () => { ... }

// --- 巨大的 Watch ---
watch(keyword, () => { ... })
</script>

当这个文件膨胀到 500 行时,维护“搜索功能”这一件事,你就需要在第 5 行、第 20 行、第 200 行之间反复横跳。这甚至比 Options API 体验更差。

深度对比与重构 (Refactoring)

如何拯救这种局面?答案是:利用 Composition API 的能力,将逻辑聚合。

Level 1: 简单的逻辑聚合 (Function Scope)

只要把相关联的代码放在一起,就已经迈出了第一步。利用折叠功能,甚至能让代码清晰度提升一个档次。

<script setup>
// Feature A: 搜索逻辑
const { keyword, searchResult, handleSearch } = (() => {
  const keyword = ref('')
  const searchResult = ref([])
  const handleSearch = () => { /* ... */ }
  return { keyword, searchResult, handleSearch }
})()

// Feature B: 分页逻辑
const { currentPage, changePage } = (() => {
   // ...
})()
</script>

注:实际写代码时不需要用立即执行函数包裹,这只是为了演示“物理位置上的聚合”。你可以直接用注释分隔,并保证相关代码紧凑排列。

Level 2: 提取 Composable (The Real Power)

真正的杀手锏是提取自定义 Hook(Composable)。

// hooks/useSearch.js
export function useSearch(apiParams) {
  const keyword = ref("");
  const data = ref([]);
  const loading = ref(false);

  const search = async () => {
    loading.value = true;
    data.value = await fetch(apiParams.url, { q: keyword.value });
    loading.value = false;
  };

  // 这里的妙处在于:watch 也被封装进去了!
  // 组件不再需要关心 keyword 变了要触发什么,Hook 内部自闭环。
  watch(keyword, search);

  return { keyword, data, loading };
}
// Component.vue
<script setup>
import { useSearch } from './hooks/useSearch'
import { usePagination } from './hooks/usePagination'

// 无论内部逻辑多复杂,在组件层面上只有这两行
const { keyword, data: list, loading } = useSearch({ url: '/api/users' })
const { currentPage, changePage } = usePagination()
</script>

现在,你的组件甚至可能只有 20 行代码。所有的业务逻辑都被拆分到了独立的、可测试的、可复用的文件中。

实战避坑指南 (Best Practices)

在重构过程中,有 3 个最常见的坑,请务必注意:

🚫 坑 1:解构丢失响应性

这是新手最容易犯的错。

// ❌ 错误做法
function useCounter() {
  const count = ref(0);
  return { count };
}

const { count } = useCounter(); // count 依然是 ref,没问题

// 但是如果是 reactive 对象...
function useUser() {
  const state = reactive({ name: "Jack", age: 18 });
  return state;
}

const { name } = useUser(); // 💥 完了,name 变成了普通字符串,失去响应性!

✅ 正确做法:尽量多用 ref,如果必须用 reactive 并需要解构返回,请使用 toRefs

🚫 坑 2:到处都是 Watch

不要把 setup 当作 watch 的大本营。很多人喜欢 watch 一个值然后修改另一个值。

❌ Bad:

const firstName = ref("John");
const lastName = ref("Doe");
const fullName = ref("");

watch([firstName, lastName], () => {
  fullName.value = firstName.value + " " + lastName.value;
});

✅ Good:

const fullName = computed(() => firstName.value + " " + lastName.value);

能用 computed 绝不用 watchcomputed 是声明式的,而 watch 是命令式的。声明式永远比命令式更好维护。

🚫 坑 3:Props 的陷阱

在 Composable 中接收 props 需要小心。

function useTitle(props) {
  // ❌ 错误:如果父组件的 props.title 变了,这里拿到的 title 不会变!
  // 因为这是 JS 值传递。
  const title = props.title;

  // ✅ 正确:传入一个 getter 或者使用 toRef
  const titleRef = toRef(props, "title");
}

建议 Composable 的参数设计为:可以接收 refgetter 函数,利用 toValue (Vue 3.3+) 来归一化。

总结与建议

Composition API 是一把双刃剑。它给了你极大的自由,也给了你写出“面条代码”的自由。

自检法则: 如果你的 script setup 超过了 200 行,且没有引入任何本地或外部的 Hook(Composable),那你大概率就在写面条代码。

建议: 下次写代码时,试着先写 Hook,再写组件。你会发现世界大不一样。

互动引导

你的项目里最长的一个 setup 有多少行?1000 行?2000 行?欢迎在评论区“比惨”。