Vue 3 组合式 API 最佳实践:如何写出可维护的代码
Vue 3 组合式 API 最佳实践:如何写出可维护的代码
核心原则:组合式 API 不是"把选项拆开",而是"按关注点聚合"
很多团队从 Options API 迁移到 Composition API 后,只是把 data、methods、computed 拆成了零散的 ref 和 function,代码反而更乱了。
问题不在 API 本身,在于组织方式。
一、第一条铁律:按功能聚合,而非按选项类型聚合
❌ 反面教材:Options 思维的平移
js
// 按类型拆分——这是 Options API 的习惯,不是 Composition API 的用法
const name = ref('')
const age = ref(0)
const getFullName = computed(() => `${name.value} ${age.value}`)
const updateName = () => { /* ... */ }
const updateAge = () => { /* ... */ }✅ 正确做法:按功能模块聚合
js
// useUser.js —— 一个功能,一个文件
import { ref, computed } from 'vue'
export function useUser() {
const name = ref('')
const age = ref(0)
const fullName = computed(() => `${name.value} ${age.value}`)
function updateName(v) { name.value = v }
function updateAge(v) { age.value = v }
return { name, age, fullName, updateName, updateAge }
}判断标准:如果你删除这个功能,应该只删一个文件,而不是从五个文件里各删一行。
二、Composables 的命名与边界
规则 | 说明 |
|---|---|
必须以 |
|
只负责逻辑,不负责 UI | Composable 返回数据和方法,不返回 JSX 或模板 |
可组合、可复用 | 如果逻辑只在一个组件里用,直接写在 |
接受参数,不读全局 | 依赖通过参数传入,不要在 composable 内部读 |
三、 的黄金结构
vue
顺序不是装饰,是阅读路径:先看依赖什么,再看逻辑做什么,最后看组件自己干什么。
四、响应式的精准控制
4.1 ref vs reactive:别纠结,记住这个
场景 | 选它 | 原因 |
|---|---|---|
原始值(string/number/boolean) |
|
|
明确知道是对象且不会整体替换 |
| 不用 |
需要整体替换(如表单重置) |
|
|
团队统一 |
| 一致性 > 便利性, |
推荐:全团队统一用 ref,少一个决策点。
4.2 toRef 和 toRefs:解构不丢失响应式
js
// ❌ 丢失响应式
const state = reactive({ count: 0, name: 'vue' })
const { count } = state // count 只是个普通数字
// ✅ 保持响应式
const { count } = toRefs(state) // count 是 ref
// 或
const count = toRef(state, 'count')五、Props 与 Emits 的类型安全
用 TypeScript 时,永远不要手写 props 定义:
ts
// ❌ 手动写,容易和实际不一致
const props = defineProps<{ title: string }>()
// ✅ 用 withDefaults + 泛型,类型推断自动对齐
const props = withDefaults(
defineProps<{
title: string
count?: number
}>(),
{ count: 0 }
)
// ✅ Emits 也一样
const emit = defineEmits<{
(e: 'update', value: number): void
(e: 'delete', id: string): void
}>()六、副作用的隔离:watchEffect vs watch
API | 何时用 | 特点 |
|---|---|---|
| 副作用依赖多个响应式值,且不关心旧值 | 自动追踪依赖,立即执行一次 |
| 精确控制:特定值变化时执行,需要旧值 | 懒执行,可选 |
| 需要在 DOM 更新后执行 | 替代 |
js
// 场景:name 变化时,同步到 localStorage
watch(name, (newVal) => {
localStorage.setItem('name', newVal)
}, { flush: 'post' }) // DOM 更新完再存,避免覆盖七、避坑清单
坑 | 正确做法 |
|---|---|
Composable 里用 | 通过参数传入,保持纯函数特性 |
在 | 拆成多个 composable,每个不超过 80 行 |
所有状态都用 | 确实会满天飞,这是代价,接受它 |
忘记 | 明确组件边界:哪些是内部的,哪些是对外的 |
|
|
循环依赖的 composable | 抽离共享状态到第三个 composable,打破环 |
八、一个真实的文件结构参考
src/
├── composables/
│ ├── useUser.ts # 用户相关逻辑
│ ├── useAuth.ts # 认证逻辑
│ ├── useFetch.ts # 通用请求封装
│ └── useTheme.ts # 主题切换
├── views/
│ └── UserProfile.vue # 只负责 UI + 调用 composable
└── App.vue当你的 views 文件夹里每个文件不超过 100 行时,架构就对了。
结语
组合式 API 的最大价值不是"更灵活",而是让你有能力把代码按人能理解的方式组织起来。
ref 和 reactive 只是工具,真正的最佳实践是:
每个文件只做一件事,每个函数只改一个地方,每次阅读只需要理解一个模块。
做到这三点,你的 Vue 3 代码就已经超过 90% 的项目了。