组合式 API
本教程共 50 篇 · 第 11 篇 · 更新于 2026-08-08 · 约 7 分钟阅读
本节目标:熟练使用 Vue 3 组合式 API 的四个基础:
ref、reactive、computed、watch,知道各自适用场景,能在<script setup>里正确读写。
上一章说了「响应式」是 Vue 的灵魂。这章就把实现响应式的工具交给你——组合式 API(Composition API)。在 Nuxt 里你几乎天天和它打交道。四个最常用:ref、reactive、computed、watch。
11-1
ref() 用来把一个值(数字、字符串、对象、数组都行)包成「响应式容器」。它返回的是一个对象,真正的值存在 .value 上。
<script setup lang="ts">
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<p>计数:{{ count }}</p>
<button @click="increment">加一</button>
</template>
注意两个地方的区别:
- 在
<script>里访问或改值,要写count.value。 - 在
<template>里直接用count,Vue 会自动帮你补上.value(这叫自动解包)。
Tip不确定该用
ref还是reactive?记一条:基本类型(数字、字符串、布尔)用ref;对象/数组用ref也完全没问题。新手统一用ref最省心,不用纠结。
11-2
reactive() 专门给对象或数组加响应式,好处是不用 .value,直接点属性访问:
<script setup lang="ts">
const user = reactive({
name: '小明',
age: 18,
})
function grow() {
user.age++
}
</script>
<template>
<p>{{ user.name }} 今年 {{ user.age }} 岁</p>
<button @click="grow">涨一岁</button>
</template>
但有坑:reactive 整个对象不能被整体替换,否则响应式会断。比如 user = { ... } 这种重新赋值会让绑定失效。所以处理「可能整体替换」的数据,还是用 ref 更稳。
Warning别把
reactive对象解构成多个独立变量(如const { name } = user),解构出来的值会失去响应性。要用就整体用user.xxx。
11-3
computed() 接收一个函数,返回一个响应式结果,而且它会「聪明地」记住自己依赖了谁:只有依赖变,它才重算;依赖没变,多次访问直接返回缓存值。
经典场景:两个输入框的值相加。
<script setup lang="ts">
const a = ref(1)
const b = ref(2)
// result 始终等于 a + b,a 或 b 一变它就自动更新
const result = computed(() => a.value + b.value)
</script>
<template>
<p>{{ a }} + {{ b }} = {{ result }}</p>
</template>
computed 默认是只读的(你不该手动给 result.value 赋值)。如果需要可写的计算属性,可以传一个带 get/set 的对象,但初学很少用到。
Note
computed和「方法(函数)」都能算出值,但区别在性能:方法每次渲染都重跑;computed有缓存,依赖不变就不重算。频繁依赖派生数据的,用computed更合适。
11-4
computed 适合「算出一个新值」,watch() 适合「值变了,去干一件事」(这叫副作用,side effect)。比如:监听登录失败次数,超过阈值就禁用按钮;或监听某个值,变化时发请求。
<script setup lang="ts">
const count = ref(0)
watch(count, (newValue, oldValue) => {
console.log(`count 从 ${oldValue} 变成了 ${newValue}`)
})
</script>
第一个参数是「要监听什么」,第二个是「变了怎么处理」的回调,回调能拿到新旧两个值。
想同时监听多个源,传数组即可:
watch([a, b], ([newA, newB]) => {
// a 或 b 变化都会触发
})
Tip
watch默认是「懒」的——只在变化后触发,不会立刻跑一次。如果你想「一开始就跑一次、之后每次变化也跑」,加第三个参数{ immediate: true }。需要深度监听嵌套对象时用{ deep: true }。
11-5
真实组件里,这四个通常一起出现:ref/reactive 存状态,computed 派生展示值,watch 响应变化做额外动作。
<script setup lang="ts">
const price = ref(100)
const quantity = ref(2)
// 派生:总价
const total = computed(() => price.value * quantity.value)
// 副作用:总价变化时记一笔日志
watch(total, (val) => {
console.log('总价更新为', val)
})
</script>
<template>
<p>单价 {{ price }},数量 {{ quantity }},合计 {{ total }}</p>
</template>
11-6
组合式 API 太好用,新手容易什么都包成响应式。但无意义的 computed/watch 多了,一个改动会触发一串重算,调试时很难追溯「到底谁动了」。
Warning写响应式时反过来问:「这个值真的会变吗?变了真的需要触发更新吗?」不变、或能靠普通函数解决的数据,就用普通变量/函数,别强加响应式。保持克制,项目大了才稳。
11-7
如果你听过 Vue 老式的「选项式 API」(data()、methods、computed 分开写),组合式 API 的区别在于:按「逻辑关注点」组织代码,而不是按「选项类型」。
选项式里,一个「计数器」功能的相关代码会被拆到 data、methods 好几处;组合式 API 里,你可以把「状态 + 派生 + 副作用」紧挨着写在一起,读一段就能看懂一个功能。在 Nuxt 里,所有 .vue 组件的 <script setup> 天然就是组合式风格。
Tip心智模型一句话:组合式 API 就是把「关于同一件事的状态和逻辑」打包成一小块代码。组件就是由这样几小块拼起来的。
11-8
当一段响应式逻辑在多个组件都要用时,别复制粘贴——把它抽成一个组合式函数,放进 app/composables/。Nuxt 会自动导入它,任何组件直接调用。
比如一个「鼠标坐标」逻辑:
// app/composables/useMouse.ts
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(e: MouseEvent) {
x.value = e.clientX
y.value = e.clientY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return { x, y }
}
组件里:
<script setup lang="ts">
const { x, y } = useMouse()
</script>
约定俗成:组合式函数以 use 开头命名(如 useFetch、useMouse),放在 composables/ 目录。Nuxt 会把它变成全局可用,不用手写 import——这正是「约定优于配置」再一次生效。
Note组合式函数里也能用
ref、computed、watch,以及生命周期钩子(onMounted等)。它本质就是一段「带响应式状态的函数」,把复用逻辑从组件里解放出来,是 Nuxt 项目里复用代码的主力方式。
11-9
ref 包单个值(脚本里用 .value,模板里自动解包);reactive 包对象但别整体替换;computed 派生带缓存的值;watch 在变化后做副作用。四个配合,几乎覆盖所有组件内的状态逻辑。下一章讲组件之间怎么用 props 和插槽通信。
11-9 组合式 API 的测试优势
组合式 API 相比选项式 API 还有一个常被忽略的优势:更易于单元测试。因为逻辑被封装在函数里,你可以直接导入并调用这些函数,传入模拟参数,验证返回值。不需要挂载整个组件就能测试核心业务逻辑。
在 Nuxt 项目里,composables/ 目录下的组合式函数可以独立编写测试。配合 Vitest(Nuxt 默认测试框架),你可以快速验证数据获取、状态管理、计算逻辑是否正确。这种测试方式比组件级测试更快、更稳定。
编写组合式函数时,建议保持每个函数的职责单一。一个函数只做一件事,测试起来简单,复用起来也方便。如果一个组合式函数超过五十行,通常意味着它承担了太多职责,值得拆分。
11-10 组合式 API 与性能优化
组合式 API 在性能方面也有优势。由于相关逻辑可以组织在一起,构建工具能更好地做代码分割和树摇(tree-shaking)。没有被使用的组合式函数会在构建时被自动移除,不会增加最终产物的体积。
在 Nuxt 里,组合式函数的自动导入也经过了优化。框架在构建时分析哪些组合式函数被实际使用了,只导入用到的部分。这意味着你可以放心地在 composables/ 目录下放很多工具函数,不用担心没用到的那些会影响包体积。