组合式 API 用 setup + ref/reactive 把逻辑按功能聚合,比选项式 API 更易复用与维护;新项目默认用它。这是编程狮讲师在带过大量学员之后给出的明确建议。很多刚接触 Vue 3 的朋友都会被两种写法搞糊涂:到底该用选项式(Options API)还是组合式(Composition API)?两者并非对立关系,而是同一套响应式内核之上的两种代码组织方式。选项式把数据、方法、计算属性分别塞进 data、methods、computed 等固定选项里,适合小型页面与老项目维护;组合式则允许你把同一块业务功能的代码写在一起,逻辑边界清晰,复用手段也更为成熟。本文会从结论、最小示例、三个核心优势、常见排错到延伸资源,把 Vue3 组合式 API 的关键点与新手最容易踩的坑一次性讲清,让你读完就能写对、用稳。

一、先看结论:Vue 3 该用组合式还是选项式
先把结论摆出来:新项目直接用 Vue3 组合式 API,老项目维护可以继续用选项式,不必强制重写。这个结论背后不是跟风,而是从可维护性与团队协作角度权衡后的结果。选项式的优点是结构固定、上手直观,初学者一眼能找到 data 在哪、methods 在哪;缺点是当组件变大、功能变多时,同一个业务功能会被拆散到 data、methods、watch、computed 等多个选项里,阅读和维护都要反复上下滚动。
组合式的优势恰恰是把\\"功能\\"而不是\\"选项类型\\"作为代码组织的单位。当你需要一个计数器,就在一处写 ref、computed、方法;当你需要一份列表数据,就在另一处写它的状态与加载逻辑。这种写法让单个功能的代码聚拢,也方便后续抽成组合式函数复用。下面这张方法选择表,帮你快速判断自己该走哪条路。
| 方式 | 适用场景 | 不推荐场景 | 推荐顺序 |
|---|---|---|---|
| 组合式 API(setup 语法) | 中大型项目、需要复用逻辑、使用 TypeScript、多人协作 | 极简单的静态展示页、临时 Demo | 1(新项目首选) |
| 选项式 API(Options API) | 老项目维护、团队刚从 Vue 2 过渡、页面逻辑很薄 | 复杂业务、需要抽离可复用逻辑 | 2(兼容与过渡用) |
如果你正在从零开始学习,编程狮建议直接以组合式为起点,再回头理解选项式会更容易。想系统了解基础语法,可参考 Vue3 教程,它从环境搭建到组件通信都有完整的图文说明。
选型看逻辑组织,不看性能差异,二者底层响应式系统一致。
二、环境与最小示例
要跑通 Vue3 组合式 API,你需要 Node.js 16 及以上版本,并用 Vite 创建一个 Vue 3 项目。组合式写法有两种入口:一种是在普通 <script> 里写 setup() 函数,另一种是用 <script setup> 语法糖。初学者先理解 setup() 函数,再学语法糖会更顺。下面给出一份最简可运行的示例,所有代码块都标注了语言,并且逐行加了中文注释,重点说明 ref 与 reactive 这两个响应式 API 的职责。
// 从 vue 包引入两个核心 API:defineComponent 用于类型推导,ref 用于声明基础类型的响应式数据
import { defineComponent, ref } from \\\'vue\\\'
// 用 defineComponent 包裹组件选项,编辑器能获得更准确的类型提示
export default defineComponent({
// setup 是组合式 API 的总入口,组件创建时只执行一次
setup() {
// ref 把数字、字符串这类\\\"基础类型\\\"包装成响应式对象;读和写都要通过 .value
const count = ref(0)
// 定义一个方法,供模板里的按钮点击调用
const increment = () => {
count.value++ // 关键点:在脚本里操作 ref 必须加 .value,否则改的是包装对象本身
}
// setup 返回的对象会被\\\"解包\\\"后暴露给模板,模板里可以直接写 {{ count }}
return { count, increment }
}
})
(以下为说明性代码,未在本机执行;预期结果:页面渲染出 count 的初始值 0,点击按钮后数字每次加 1,且视图自动刷新。)
上面示例里 ref(0) 的作用是让一个普通数字变成\\"响应式\\"的:当它的值在脚本中被修改时,Vue 会自动通知用到它的视图重新渲染。与之对应的 reactive 则用于对象或数组这类\\"引用类型\\"。下面补一段 reactive 的示例,同样逐行注释,说明它和 ref 的关系。建议你在本地用 Vite 运行这份示例,检查计数器是否随点击自增、视图是否随 .value 变化,配置好 Node 环境后再做扩展验证。
// 引入 reactive,用于把对象变成响应式
import { reactive } from \\\'vue\\\'
// reactive 直接包裹一个普通对象,返回一个深层响应式的代理对象
const state = reactive({
name: \\\'编程狮\\\', // 字符串属性,直接通过 state.name 读写,无需 .value
age: 3
})
// 修改属性会触发视图更新,语法和改普通对象一样自然
state.age = 4
关于 ref 与 reactive 的取舍,经验法则是:基础类型用 ref,对象/数组用 reactive;如果你希望统一风格、减少记忆负担,也可以全部用 ref(对象用 ref({...}),访问时 state.value.x)。更多基础概念可以看 Vue.js 教程,它对比了 Vue 2 与 Vue 3 的演进。
三、优势一:逻辑按功能聚合
组合式 API 的第一个、也是最直观的优势,是让\\"同一功能\\"的代码聚在一起。在选项式里,一个\\"用户搜索\\"功能可能要把关键词写在 data、过滤方法写在 methods、防抖逻辑写在 watch;而在组合式里,你可以把这三段代码紧挨着写,逻辑一目了然。下面用一段示例展示 setup 的组织方式,逐行注释说明每一块在做什么。
// 引入需要的 API:ref 管状态,computed 管派生值,onMounted 是生命周期钩子
import { ref, computed, onMounted } from \\\'vue\\\'
export default {
setup() {
// —— 功能一:计数器 —— 状态、派生值、方法写在一处
const count = ref(0) // 计数状态
const double = computed(() => count.value * 2) // 派生:自动随 count 变化
const add = () => count.value++ // 修改方法,和状态相邻,好找也好改
// —— 功能二:用户列表 —— 另一块功能独立成段
const users = ref([]) // 列表状态
const loadUsers = async () => { // 加载方法
users.value = await fetch(\\\'/api/users\\\').then(r => r.json())
}
onMounted(loadUsers) // 生命周期钩子紧挨着它要服务的逻辑
// 一次性把所有要暴露给模板的内容返回
return { count, double, add, users }
}
}
(以下为说明性代码,未在本机执行;预期结果:组件挂载后自动请求用户列表并渲染,计数器点击后 double 同步翻倍。)
这种\\"按功能聚合\\"的写法带来的好处非常实在。第一,阅读代码时不必在 data、methods、watch 之间反复跳转;第二,当某个功能要调整,你只需要盯着那一段,不会误伤其他逻辑;第三,这种结构天然适合下一步的复用——把一段功能整体搬走即可。这正是 Vue3 组合式 API 被推荐作为新项目默认写法的根本原因。
组件功能变多时,组合式的可读性优势会更明显。
四、优势二:组合式函数复用
第二个优势是组合式函数(Composables)带来的逻辑复用能力。所谓组合式函数,就是命名以 use 开头、内部调用 Vue 响应式 API、最后返回响应式状态与方法的一个普通函数。它的价值在于:同一段逻辑可以在任意组件里\\"即插即用\\",而不必像选项式那样依赖 mixin(mixins 容易命名冲突、来源不清晰)。下面演示如何把一个计数器抽成 useCounter,并说明为什么要这样设计。
// useCounter.js —— 把\\\"计数器\\\"逻辑抽成可复用函数,文件名以 use 开头是约定
import { ref, computed } from \\\'vue\\\'
// 接收初始值,默认 0;通过参数让调用方自定义
export function useCounter(initial = 0) {
const count = ref(initial) // 内部状态,调用方拿不到原始引用,只能拿返回值
const double = computed(() => count.value * 2) // 派生值也一并封装
const increment = () => count.value++ // 只暴露\\\"受控\\\"的修改方法,避免外部乱改
return { count, double, increment } // 返回响应式引用,模板可直接解构使用
}
(以下为说明性代码,未在本机执行;预期结果:任意组件调用 useCounter 都能得到独立的计数器状态,彼此互不干扰。)
在组件里使用它极其简单,解构后直接返回给模板即可:
// 引入刚才写好的组合式函数
import { useCounter } from \\\'./useCounter\\\'
export default {
setup() {
// 复用计数器逻辑,传入初始值 10;返回的都是响应式引用
const { count, double, increment } = useCounter(10)
// 直接返回,模板里就能用 {{ count }} 和 @click=\\\"increment\\\"
return { count, double, increment }
}
}
(以下为说明性代码,未在本机执行;预期结果:该组件渲染出从 10 开始的计数器,点击后递增,double 同步为两倍。)
组合式函数的好处有三:其一,逻辑来源清晰,一眼能看出 count 来自 useCounter;其二,没有 mixin 的命名覆盖风险,因为你可以自己决定解构成什么变量名;其三,组合式函数之间还能互相调用,比如 useUser 内部可以调用 useFetch,从而搭建出一层可复用的逻辑库。可以说,Vue3 组合式 API 的复用能力,是它相比选项式最被低估、却最值得掌握的一点。
五、优势三:更好的 TypeScript 支持与排错
第三个优势是更顺滑的 TypeScript 体验与更清晰的排错路径。组合式 API 因为直接返回普通对象、显式使用 ref/reactive,类型推导比选项式更自然,IDE 能准确提示响应式对象的属性。但新手在写组合式时也容易踩几个固定的坑,下面用一张排错表把最常见的四类问题列出来,三列分别是现象、常见原因、修复方向。
| 现象 | 常见原因 | 修复方向 |
|---|---|---|
模板里显示 [object Object],或脚本中值一直不变 |
ref 忘记写 .value |
在 <script> 内读写 ref 一律用 .value;模板中会自动解包,不要再加 .value |
| 明明改了数据,视图却不刷新 | 响应式丢失:把 reactive 根对象整体重新赋值 |
用 Object.assign(state, newObj) 合并,或改用 ref 包裹;不要直接 state = xxx |
| 生命周期钩子不执行或报\\"实例不存在\\" | 钩子写在异步回调或 if 条件分支内部 |
钩子(如 onMounted)必须同步写在 setup 顶层,不能放进函数或条件里 |
| 解构后数据不再响应 | 直接 const { x } = reactiveObj 解构丢失响应 |
用 toRefs(reactiveObj) 把每个属性转成 ref 再解构,保持响应 |
(以下为说明性代码,未在本机执行;预期结果:按\\"修复方向\\"调整后,上述四类问题对应的视图均能正常响应更新。)
排错表里的第一条\\"ref 忘记 .value\\"是出现频率最高的错误。要记住一条简单规则:在脚本里,ref 是一个包装对象,必须通过 .value 访问它包裹的真实值;而在模板里,Vue 会自动帮你解包,直接写变量名即可。第二条\\"响应式丢失\\"常发生在用 reactive 时,因为 Vue 的响应式是基于代理的,整体替换根对象会切断代理。第三条关于生命周期钩子位置,是很多从选项式迁移过来的同学会犯的错——选项式里钩子本身就是顶层选项,组合式里则必须显式调用且放在顶层。第四条\\"解构失响应\\",用 toRefs 就能解决。
从 TypeScript 角度看,setup 语法配合 defineComponent 或 <script setup>,能让 ref、reactive、props 的类型都被准确推导,几乎不需要手写大量泛型。这也是为什么在需要类型约束的中大型团队项目中,Vue3 组合式 API 几乎成了默认选择。

总结
回到开头那句话:组合式 API 用 setup + ref/reactive 把逻辑按功能聚合,比选项式 API 更易复用与维护,新项目默认用它。我们先用一张方法选择表厘清了选型边界,再通过最小示例理解了 ref 与 reactive 的职责,随后展开讲了三个核心优势——逻辑按功能聚合、组合式函数复用、更好的 TypeScript 支持与排错,最后用一张排错表把新手最常踩的四个坑逐一拆解。掌握 setup 语法、分清 ref 与 reactive 的边界、学会把逻辑抽成组合式函数,这三步做完,你就已经跨过了 Vue 3 入门最关键的门槛。后续在真实项目里多写几个 useXxx,自然就能体会到组合式 API 在可维护性上的长期红利。
延伸学习
- Vue3 入门课程 —— 零基础到能写项目的短视频实战课。
- Vue 与 HTML 区别 —— 一篇笔记,讲清 Vue 模板与原生 HTML 的本质差异。
- Vue2 教程 —— 老项目从 Vue 2 迁移时的对照参考。
常见问题
Q:组合式 API 和 <script setup> 语法糖是什么关系?
两者不是二选一。组合式 API 是一种\\"代码组织思想\\",可以用普通 setup() 函数实现,也可以用 <script setup> 这种编译期语法糖实现。语法糖只是让你少写 export default 和 return,组件逻辑还是那套 ref、reactive、生命周期钩子。新手先理解 setup() 函数,再切换到 <script setup> 会非常自然,因为底层 API 完全一致。
Q:ref 和 reactive 到底该怎么选,能不能混用?
可以混用,但建议有统一习惯。经验法则是:基础类型(数字、字符串、布尔)用 ref;对象、数组用 reactive。如果你嫌记规则麻烦,也可以全部用 ref,对象就写成 ref({ ... }),读取时通过 state.value.x。关键是团队内部保持一致,避免同一个项目里一会儿 ref 一会儿 reactive 造成阅读负担。无论选哪种,记住 ref 在脚本里要加 .value 这一条铁律。
Q:老项目从选项式迁移到 Vue3 组合式 API 一定要重写吗?
不需要一步到位。Vue 3 同时支持选项式和组合式,两者可以在同一个项目、甚至同一个组件里共存。推荐策略是:新页面、新组件直接用组合式写;老组件如果逻辑没出问题,先保留选项式,等哪天要改这块功能时再顺手用组合式重写那一部分。这样既享受了组合式的复用与维护优势,又避免了一次性大规模重构带来的风险。

TRAE-AI编程



