外观
胖组件 vs 瘦组件
是什么
一个 .vue 文件里,模板、状态、请求、格式化、交互逻辑全塞在一起,就是胖组件;把逻辑抽进 composable / 工具函数,组件只剩「拿到数据 → 画出来 → 报告事件」,就是瘦组件。
这不是代码洁癖。赌的是六个月后的你还敢不敢改这个文件:胖组件每改一处都要在脑子里同时装下全部逻辑,瘦组件每次只装一段。
优劣对比
| 胖组件 | 瘦组件 + composable | |
|---|---|---|
| 上手速度 | ⭐⭐⭐⭐ 快,全在一个文件 | ⭐⭐ 要跳文件看逻辑 |
| 改动安全 | ⭐ 牵一发动全身 | ⭐⭐⭐⭐ 只碰相关那一段 |
| 复用 | ⭐ 复制粘贴,改两处忘一处 | ⭐⭐⭐⭐⭐ useXxx 直接 import |
| 测试 | ⭐ 几乎只能手点 | ⭐⭐⭐⭐ 纯函数/composable 可单测 |
| 适用 | 一次性页面、 demo | 会活过两周的业务代码 |
怎么判断:抽离的信号(不是行数)
不要用「超过 300 行就拆」这种伪规则,用这三个信号:
- 同一段逻辑第二处出现 → 立刻抽成 composable。第二处出现时手感和第一处不同:你已经知道要抄哪几行了,那几行就是
useXxx的边界。 - 模板里的表达式开始看不懂 → 三元套三元、
items.filter(...).map(...).sort(...)内联在模板里 → 挪进computed,给个名字。模板应该读起来像句子,不像作业题。 <script setup>能按「请求 / 格式化 / 交互」分出明显段落 → 每段一个 composable,组件里剩下一排const { x } = useXxx()。
反信号(别抽):
- 只有一处使用、十行以内的逻辑——抽出去反而要多跳一个文件
- 为了「以后可能复用」而抽——没有第二处之前,「以后」基本不来
抽成什么样
最小套路:把「状态 + 改它的函数」原样搬进 useXxx,返回一个对象。
ts
// composables/useCounter.ts —— 抽之前它在组件里,抽之后谁都能用
import { ref } from 'vue'
export function useCounter(initial = 0) {
const count = ref(initial)
function increment() {
count.value++
}
return { count, increment }
}vue
<script setup>
import { useCounter } from '@/composables/useCounter'
const { count, increment } = useCounter()
</script>抽完自检一条:composable 里不应该出现 DOM 和「这个页面」的概念。出现了,说明抽的是页面,不是逻辑。
结论一句话
默认写胖;出现「第二处出现 / 模板看不懂 / 脚本能分段」任一信号就抽。抽的单位是逻辑(composable),不是行数。
参考链接
- Vue 官方 · 组合式函数——抽法与约定的权威版本
- patterns.dev——组件设计模式的系统性梳理