人人都会AI编程

8.2 组合式 API(Composition API)

更新时间:2026-07-09

组合式 API 是 Vue 3 引入的一套全新的逻辑组织和代码复用方式。它允许你在同一个作用域内集中处理某一功能的响应式数据、计算属性、侦听器、生命周期钩子等,而不是像选项式 API 那样按配置项分散到 datamethodscomputedwatch 等不同区域。

最直观的好处是:当一个组件的功能变复杂时,相关逻辑可以天然地聚合在一起,不用在多个选项块之间来回跳转。

两种写法:setup() 函数与 <script setup> 语法糖

组合式 API 有两种书写方式。传统方式是在组件选项中定义一个 setup() 函数,所有组合式 API 都在这个函数内部调用,并返回模板需要的数据和方法:

<script>
import { ref, computed } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const double = computed(() => count.value * 2)
    function increment() {
      count.value++
    }
    // 模板中需要用到的变量/方法,必须在这里显式返回
    return { count, double, increment }
  }
}
</script>

<script setup> 是官方推荐的语法糖,它消隐了 setup() 函数和 return 语句,让代码更简洁:

<script setup>
import { ref, computed } from 'vue'

const count = ref(0)
const double = computed(() => count.value * 2)
function increment() {
  count.value++
}
// 无需 return,顶层绑定会自动暴露给模板
</script>

两者完全等价,但 <script setup> 减少了样板代码,同时配合 TypeScript 和 IDE 支持更流畅。在实际项目中,几乎都选择 <script setup> 作为默认写法。唯一的制约是:<script setup> 中不能再使用 export default 单独导出其他内容(如组件级选项),需要用专门的编译器宏(如 definePropsdefineEmitsdefineExpose)完成。

核心响应式 API

组合式 API 中常用的响应式工具主要有以下几个:

1. ref
用于包装任意类型的值(基本类型或对象),使其成为响应式数据。通过 .value 来读写:

import { ref } from 'vue'

const count = ref(0)        // 基本类型
const user = ref({ name: 'Alice' })  // 对象

// 读取/修改时都要用 .value
console.log(count.value)
count.value++
user.value.name = 'Bob'

在模板中自动解包,不需要 .value

<span>{{ count }}</span> <!-- 直接写 count,不用 count.value -->

2. reactive
直接包裹一个对象,将其整体变成响应式。不需要 .value,直接像原生对象一样读写:

import { reactive } from 'vue'

const state = reactive({ count: 0, name: 'Alice' })
state.count++   // 直接操作属性
state.name = 'Bob'

reactive 有两个容易踩坑的限制:

  • 不能整体替换(state = reactive({ count: 1 }) 会失去响应式)。
  • 对象解构会丢失响应式(必须搭配 toRefs 使用)。

3. toReftoRefs
这两个 API 主要用于保持对象解构后的响应式连接

const state = reactive({ x: 1, y: 2 })

// ❌ 直接解构会导致 x 和 y 失去响应式
const { x, y } = state

// ✅ 用 toRefs 保留响应式
const { x, y } = toRefs(state)
// 现在 x.value 修改会同步更新 state.x

toRef 用来单独为 props 或 reactive 对象的某个属性创建引用,常用于将某个属性传给组合式函数且保持其响应性。

4. unref
一个便捷工具:若参数是 ref 则取 .value,否则直接返回原值。写通用 composable 时非常实用。

计算属性:computed

computed 的用法与选项式 API 几乎一致,只是换成了函数调用形式:

const count = ref(2)
const double = computed(() => count.value * 2)

计算属性默认是只读的,也可以创建可写计算属性:

const double = computed({
  get: () => count.value * 2,
  set: (val) => { count.value = val / 2 }
})
double.value = 6  // count 自动变为 3

计算属性的缓存机制在组合式 API 中依然生效:只有当其依赖的响应式数据发生变化时,才会重新求值。

侦听器:watchwatchEffectwatchPostEffect

watch
用于手动指定一个或多个响应式数据源,并在它们变化时执行副作用:

const count = ref(0)
const name = ref('Alice')

// 侦听单个 ref
watch(count, (newVal, oldVal) => {
  console.log(`count 从 ${oldVal} 变为 ${newVal}`)
})

// 侦听多个数据源
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
  // ...
})

如果需要深度监听 reactive 对象内部属性的变化,需要显式传入 { deep: true }。当数据源是 ref 包裹的对象时,默认只监听引用变化,同样需要 deep

watchEffect
它会自动收集在回调函数内部被访问过的响应式依赖,并在依赖变化时立即重新执行:

watchEffect(() => {
  // 自动追踪 count.value 和 name.value
  console.log(count.value, name.value)
})

watch 的区别:

  • watch 是惰性的,初始不会执行;watchEffect 在创建时立即执行一次。
  • watchEffect 无需手动指定依赖,依赖自动追踪(但无法获取旧值)。
  • watchEffect 更适合执行“只要数据变了就用最新数据做点事情”的场景,比如初始化请求、自动保存。

watchPostEffect
它是 watchEffect 的变种,保证在 DOM 更新完成后执行副作用,等同于 watchEffect 设置 { flush: 'post' }。常用于需要访问更新后的 DOM 的情况,防范时序问题。

生命周期钩子

组合式 API 的生命周期钩子形式是在 Vue 的 API 名称前加上 on 前缀。它们必须在 setup 期间调用(同步),并且不需要像选项式 API 那样把逻辑写在某个特定选项下。

import { onMounted, onUnmounted, onUpdated } from 'vue'

onMounted(() => {
  console.log('组件已挂载')
})
onUnmounted(() => {
  clearInterval(timer)
})

映射关系如下:

| 选项式 API | 组合式 API |
|----------------|------------------|
| beforeCreate | 直接在 setup 中写(不需对应钩子) |
| created | 直接在 setup 中写 |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeUnmount | onBeforeUnmount |
| unmounted | onUnmounted |
| errorCaptured | onErrorCaptured |

注意,setup 本身就在 beforeCreatecreated 之间执行,因此这两个阶段的初始化代码直接放在 <script setup> 顶层即可,无需对应的钩子。

真实应用场景举例

假设一个用户资料页面,需要加载用户数据并实时显示在线状态,同时支持搜索输入防抖。用组合式 API 可以把这些逻辑各自封装成清晰的组合式函数,然后在组件中“组装”它们:

<script setup>
import { useUser } from '@/composables/useUser'
import { useOnlineStatus } from '@/composables/useOnlineStatus'
import { useDebounce } from '@/composables/useDebounce'

const { user, loading } = useUser()
const { isOnline } = useOnlineStatus(user)
const { debouncedSearch, searchInput } = useDebounce()
</script>

每个功能点都是一个自包含的 JavaScript 模块,可单独测试,可在多个组件间复用——这正是组合式 API 带来的最大生产力提升。在简单的组件里你可以仍然用选项式 API,但当逻辑膨胀到需要“逻辑关注点分离”时,组合式 API 会让代码组织变得从容。