Vue 3 组合式 API 最佳实践:如何写出可维护的代码

Vue 3 组合式 API 最佳实践:如何写出可维护的代码


核心原则:组合式 API 不是"把选项拆开",而是"按关注点聚合"

很多团队从 Options API 迁移到 Composition API 后,只是把 datamethodscomputed 拆成了零散的 reffunction,代码反而更乱了。

问题不在 API 本身,在于组织方式


一、第一条铁律:按功能聚合,而非按选项类型聚合

❌ 反面教材:Options 思维的平移

javascript 复制代码
js
// 按类型拆分------这是 Options API 的习惯,不是 Composition API 的用法
const name = ref('')
const age = ref(0)
const getFullName = computed(() => `${name.value} ${age.value}`)
const updateName = () => { /* ... */ }
const updateAge = () => { /* ... */ }

✅ 正确做法:按功能模块聚合

javascript 复制代码
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 的命名与边界

规则 说明
必须以 use 开头 useUseruseFetchuseTheme,这是社区约定,也是 Vue 生态的识别标志
只负责逻辑,不负责 UI Composable 返回数据和方法,不返回 JSX 或模板
可组合、可复用 如果逻辑只在一个组件里用,直接写在 <script setup> 里就好,别硬拆
接受参数,不读全局 依赖通过参数传入,不要在 composable 内部读 inject 以外的全局状态

三、<script setup> 的黄金结构

xml 复制代码
vue
<script setup lang="ts">
// 1. 引入 ------ 外部依赖
import { ref, computed, onMounted } from 'vue'
import { useUser } from '@/composables/useUser'
import { useFetch } from '@/composables/useFetch'

// 2. 逻辑层 ------ 所有 composable 调用
const { name, age, fullName } = useUser()
const { data, loading, error } = useFetch('/api/users')

// 3. 组件专属状态 ------ 只属于当前组件的东西
const showModal = ref(false)

// 4. 计算属性
const isAdult = computed(() => age.value >= 18)

// 5. 生命周期与副作用
onMounted(() => {
  console.log('mounted')
})

// 6. 对外暴露 ------ 给父组件用的
defineExpose({ showModal })
</script>

顺序不是装饰,是阅读路径:先看依赖什么,再看逻辑做什么,最后看组件自己干什么。


四、响应式的精准控制

4.1 ref vs reactive:别纠结,记住这个

场景 选它 原因
原始值(string/number/boolean) ref reactive 无法包裹原始值
明确知道是对象且不会整体替换 reactive 不用 .value,写起来干净
需要整体替换(如表单重置) ref ref.value = {} 一步到位
团队统一 ref 一致性 > 便利性,ref 适用所有场景

推荐:全团队统一用 ref,少一个决策点。

4.2 toReftoRefs:解构不丢失响应式

php 复制代码
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 定义

typescript 复制代码
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 何时用 特点
watchEffect 副作用依赖多个响应式值,且不关心旧值 自动追踪依赖,立即执行一次
watch 精确控制:特定值变化时执行,需要旧值 懒执行,可选 immediate: true
watch(..., { flush: 'post' }) 需要在 DOM 更新后执行 替代 nextTick 的大多数场景
javascript 复制代码
js
// 场景:name 变化时,同步到 localStorage
watch(name, (newVal) => {
  localStorage.setItem('name', newVal)
}, { flush: 'post' })  // DOM 更新完再存,避免覆盖

七、避坑清单

正确做法
Composable 里用 inject 读取全局状态 通过参数传入,保持纯函数特性
setup 里写几百行逻辑 拆成多个 composable,每个不超过 80 行
所有状态都用 ref 导致 .value 满天飞 确实会满天飞,这是代价,接受它
忘记 defineExpose 导致父组件拿不到方法 明确组件边界:哪些是内部的,哪些是对外的
computed 里写副作用 computed 必须是纯函数,有副作用用 watchEffect
循环依赖的 composable 抽离共享状态到第三个 composable,打破环

八、一个真实的文件结构参考

bash 复制代码
src/
├── composables/
│   ├── useUser.ts          # 用户相关逻辑
│   ├── useAuth.ts          # 认证逻辑
│   ├── useFetch.ts         # 通用请求封装
│   └── useTheme.ts         # 主题切换
├── views/
│   └── UserProfile.vue     # 只负责 UI + 调用 composable
└── App.vue

当你的 views 文件夹里每个文件不超过 100 行时,架构就对了。


结语

组合式 API 的最大价值不是"更灵活",而是让你有能力把代码按人能理解的方式组织起来

refreactive 只是工具,真正的最佳实践是:

每个文件只做一件事,每个函数只改一个地方,每次阅读只需要理解一个模块。

做到这三点,你的 Vue 3 代码就已经超过 90% 的项目了。

相关推荐
世界哪有真情4 小时前
拿人类意识卡 AI?等于用 bug 验收正式产品
前端·人工智能·后端
Listen·Rain5 小时前
Vue3:setup详解
前端·javascript·vue.js
Patrick_Wilson6 小时前
修好 bug 只是开始:一次由监控需求反向重构日志结构的复盘
前端·监控·数据可视化
kyriewen6 小时前
面试官让我优化一个卡死的表格,我打开 Claude 五秒重写,他放下咖啡问我要不要来当组长
前端·javascript·面试
小粉粉hhh7 小时前
React(二)——dom、组件间通信、useEffect
前端·javascript·react.js
Csvn7 小时前
🤖 AI 生成前端代码的 5 个典型翻车现场及修复指南
前端
IT_陈寒8 小时前
SpringBoot自动装配坑了我一天,原来问题出在这
前端·人工智能·后端
索西引擎8 小时前
【React】key 属性:协调算法中的元素标识机制与最佳实践
前端·javascript·react.js
只会cv的前端攻城狮9 小时前
动态组件架构设计:从配置到事件驱动全链路解析
前端·vue.js
飞天狗9 小时前
Next.js 16 App Router 实战:服务端组件与流式渲染
前端·next.js