Vuejs

Vue.js 3 Composition API 完全指南:2026 版

掌握 2026 年 Vue 3 Composition API:從基本概念到高級模式。學習 setup 腳本語法、響應式狀態、組合式函數、生命週期鉤子、TypeScript 集成和可擴展 Vue 應用的最佳實踐。

Vue.js 3 Composition API 完全指南:2026 版

Vue.js 3 引入了一种革命性的组件编写方式:Composition API。在 2026 年,随着 Vue 2 达到生命周期终点,掌握 Composition API 不再是可选项 —— 它是现代 Vue 开发的必备技能。

本指南涵盖 Vue 3 Composition API 的所有知识 —— 从基本概念到高级模式和最佳实践。

Vue 3 vs Vue 2 - 有什么变化?

在深入 Composition API 之前,让我们了解为什么 Vue 3 是游戏规则改变者。

Vue 2(Options API):

export default {
  data() {
    return {
      count: 0,
      user: null
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2
    }
  }
}
Code

Vue 3(Composition API):

import { ref, computed } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const user = ref(null)

    const increment = () => {
      count.value++
    }

    const doubleCount = computed(() => count.value * 2)

    return { count, user, increment, doubleCount }
  }
}
Code

关键差异:

Vue 2 Options API Vue 3 Composition API
按选项组织(datamethodscomputed 逻辑关注点组织
this 上下文可能令人困惑 没有 this —— 只有导入
跨组件重用逻辑困难 使用组合式函数轻松重用
TypeScript 支持有限 一流的 TypeScript 支持

Composition API vs Options API

何时使用 Composition API

✅ 适用于: - 大型应用 - 逻辑复杂的组件 - 重视代码可重用性的团队 - TypeScript 项目 - 需要在组件间共享的逻辑

✅ Options API 仍然适用于: - 简单组件 - 小型项目 - 熟悉 Vue 2 的团队 - 快速原型

代码组织对比

Options API(按选项类型):

// UserComponent.vue
export default {
  data() {
    return {
      // 用户数据
      userName: '',
      userEmail: '',
      // 帖子数据
      posts: [],
      loading: false
    }
  },
  methods: {
    // 用户方法
    updateName() { },
    updateEmail() { },
    // 帖子方法
    fetchPosts() { },
    deletePost() { }
  }
}
Code

Composition API(按逻辑关注点):

// UserComponent.vue
import { useUser } from './composables/useUser'
import { usePosts } from './composables/usePosts'

export default {
  setup() {
    const { userName, userEmail, updateName, updateEmail } = useUser()
    const { posts, loading, fetchPosts, deletePost } = usePosts()

    return { userName, userEmail, posts, loading, updateName, deletePost }
  }
}
Code

好处: 相关逻辑保持在一起,使代码更易于理解和维护。

Setup 脚本语法(<script setup>

Vue 3.2 引入了 <script setup> —— 一种编译时语法糖,使 Composition API 更简洁。

基本用法

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

// 响应式状态
const count = ref(0)

// 计算属性
const doubleCount = computed(() => count.value * 2)

// 方法
const increment = () => {
  count.value++
}
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double: {{ doubleCount }}</p>
    <button @click="increment">Add</button>
  </div>
</template>
Code

<script setup> 的好处: - ✅ 更少的样板代码(无需 setup() 函数) - ✅ 自动导入 setup 绑定 - ✅ 更好的 TypeScript 推断 - ✅ 更高效的运行时性能

使用 TypeScript

<script setup lang="ts">
import { ref } from 'vue'

// 类型化的 ref
const count = ref<number>(0)

// 类型化的事件
const handleClick = (event: MouseEvent) => {
  console.log(event.clientX)
}

// 类型化的 props
interface Props {
  title: string
  count?: number
}

const props = withDefaults(defineProps<Props>(), {
  count: 0
})

// 类型化的 emits
const emit = defineEmits<{
  (e: 'update', value: number): void
}>()
</script>
Code

响应式状态:ref()reactive()

Vue 3 提供两种主要的响应式状态创建方式。

ref() - 用于原始值

import { ref } from 'vue'

const count = ref(0)
const name = ref('John')
const isActive = ref(true)

// 通过 .value 访问
count.value++
name.value = 'Jane'
Code

ref() 适用于: - 数字、字符串、布尔值 - 对象(当你想替换整个对象时) - 数组

reactive() - 用于对象

import { reactive } from 'vue'

const state = reactive({
  count: 0,
  name: 'John',
  isActive: true
})

// 无需 .value
state.count++
state.name = 'Jane'
Code

reactive() 适用于: - 具有多个属性的复杂对象 - 当你想组合相关状态时

ref() vs reactive() 对比

特性 ref() reactive()
原始值 ✅ 是 ❌ 否
对象 ✅ 是 ✅ 是
访问 .value 直接
替换整个对象 ✅ 是 ❌ 否(失去响应性)
TypeScript 推断 更好 良好

建议: 一致使用 ref() 以获得更简单的心智模型。

计算属性和侦听器

计算属性

计算属性根据其依赖进行缓存

import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

// 计算属性
const fullName = computed(() => {
  return `${firstName.value} ${lastName.value}`
})

// 默认只读
console.log(fullName.value) // "John Doe"

// 使其可写
const editableFullName = computed({
  get: () => `${firstName.value} ${lastName.value}`,
  set: (value) => {
    [firstName.value, lastName.value] = value.split(' ')
  }
})
Code

侦听器

侦听器在依赖变化时执行副作用

import { ref, watch } from 'vue'

const searchQuery = ref('')

// 侦听单个 ref
watch(searchQuery, (newVal, oldVal) => {
  console.log(`搜索从 ${oldVal} 变为 ${newVal}`)
  // 执行 API 搜索
})

// 侦听多个源
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
  console.log('姓名已更改')
})

// 带选项的侦听
watch(searchQuery, async (newVal) => {
  const results = await fetchResults(newVal)
}, {
  debounce: 300,  // 在最后一次更改后等待 300ms
  immediate: true // 挂载时立即运行
})
Code

watch() vs watchEffect()

import { ref, watch, watchEffect } from 'vue'

const userId = ref(1)
const user = ref(null)

// watch - 显式依赖
watch(userId, async (newId) => {
  user.value = await fetchUser(newId)
})

// watchEffect - 自动依赖跟踪
watchEffect(async () => {
  user.value = await fetchUser(userId.value)
})
Code

使用 watch() 当: - 你需要旧值和新值 - 你想侦听特定源 - 你需要控制效果何时运行

使用 watchEffect() 当: - 你只需要当前值 - 你想要自动依赖跟踪 - 你希望效果立即运行

组合式函数(可重用逻辑)

组合式函数是 Composition API 的超能力 —— 封装和返回响应式状态的可重用函数。

创建组合式函数

// composables/useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)

  const doubleCount = computed(() => count.value * 2)

  const increment = () => {
    count.value++
  }

  const decrement = () => {
    count.value--
  }

  const reset = () => {
    count.value = initialValue
  }

  return { count, doubleCount, increment, decrement, reset }
}
Code

使用组合式函数

<script setup>
import { useCounter } from './composables/useCounter'

const { count, doubleCount, increment, reset } = useCounter(10)
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double: {{ doubleCount }}</p>
    <button @click="increment">+</button>
    <button @click="reset">Reset</button>
  </div>
</template>
Code

实际示例:useFetch

// composables/useFetch.js
import { ref } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const loading = ref(true)
  const error = ref(null)

  const fetchData = async () => {
    loading.value = true
    error.value = null

    try {
      const response = await fetch(url)
      if (!response.ok) throw new Error('网络响应不正常')
      data.value = await response.json()
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }

  // 挂载时获取
  fetchData()

  return { data, loading, error, refetch: fetchData }
}
Code

用法:

<script setup>
import { useFetch } from './composables/useFetch'

const { data: users, loading, error, refetch } = useFetch('/api/users')
</script>

<template>
  <div>
    <div v-if="loading">加载中...</div>
    <div v-else-if="error">错误:{{ error }}</div>
    <div v-else>
      <ul>
        <li v-for="user in users" :key="user.id">{{ user.name }}</li>
      </ul>
      <button @click="refetch">刷新</button>
    </div>
  </div>
</template>
Code

Composition API 中的生命周期钩子

Vue 3 生命周期钩子直接从 Options API 映射到 Composition API。

Options API Composition API 何时
beforeCreate setup() 组件初始化
created setup() 响应式数据后
beforeMount onBeforeMount() DOM 插入前
mounted onMounted() DOM 插入后
beforeUpdate onBeforeUpdate() 重新渲染前
updated onUpdated() 重新渲染后
beforeDestroy onBeforeUnmount() 卸载前
destroyed onUnmounted() 卸载后

示例

import { 
  ref, 
  onMounted, 
  onUpdated, 
  onBeforeUnmount 
} from 'vue'

export default {
  setup() {
    const element = ref(null)

    onMounted(() => {
      console.log('组件已挂载')
      // 安全访问 DOM
      console.log(element.value)
    })

    onUpdated(() => {
      console.log('组件已更新')
    })

    onBeforeUnmount(() => {
      console.log('组件将卸载')
      // 清理:移除事件监听器、取消计时器
    })

    return { element }
  }
}
Code

TypeScript 集成

Vue 3 具有一流的 TypeScript 支持,Composition API 与之配合完美。

类型化的 Props

<script setup lang="ts">
interface Props {
  title: string
  count?: number
  isActive: boolean
}

// 带默认值
const props = withDefaults(defineProps<Props>(), {
  count: 0,
  isActive: true
})
</script>
Code

类型化的 Emits

<script setup lang="ts">
// 定义 emit 类型
const emit = defineEmits<{
  (e: 'update', value: number): void
  (e: 'close'): void
  (e: 'change', name: string, value: any): void
}>()

// 用法
emit('update', 42)
emit('close')
emit('change', 'name', 'John')
</script>
Code

类型化的组合式函数

// composables/useUser.ts
import { ref } from 'vue'

interface User {
  id: number
  name: string
  email: string
}

export function useUser() {
  const user = ref<User | null>(null)
  const loading = ref(false)
  const error = ref<string | null>(null)

  const fetchUser = async (id: number) => {
    loading.value = true
    try {
      const response = await fetch(`/api/users/${id}`)
      user.value = await response.json()
    } catch (err) {
      error.value = (err as Error).message
    } finally {
      loading.value = false
    }
  }

  return { user, loading, error, fetchUser }
}
Code

从 Options API 迁移指南

逐步迁移

1. 从 <script setup> 开始

<!-- 之前 -->
<script>
export default {
  data() { return { count: 0 } },
  methods: { increment() { this.count++ } }
}
</script>

<!-- 之后 -->
<script setup>
import { ref } from 'vue'

const count = ref(0)
const increment = () => count.value++
</script>
Code

2. 转换计算属性

// 之前
computed: {
  doubleCount() {
    return this.count * 2
  }
}

// 之后
const doubleCount = computed(() => count.value * 2)
Code

3. 转换侦听器

// 之前
watch: {
  searchQuery(newVal) {
    this.fetchResults(newVal)
  }
}

// 之后
watch(searchQuery, (newVal) => {
  fetchResults(newVal)
})
Code

4. 将逻辑提取到组合式函数中

// composables/useSearch.js
export function useSearch(fetchFn) {
  const query = ref('')
  const results = ref([])
  const loading = ref(false)

  const search = async () => {
    loading.value = true
    results.value = await fetchFn(query.value)
    loading.value = false
  }

  return { query, results, loading, search }
}
Code

常见陷阱

❌ 忘记 ref 的 .value

const count = ref(0)
console.log(count) // 错误!
console.log(count.value) // 正确
Code

❌ 解构响应式对象:

const state = reactive({ count: 0 })
const { count } = state // 失去响应性!

// 使用 toRefs
import { toRefs } from 'vue'
const { count } = toRefs(state) // 保持响应性
Code

❌ 在 Options API 组件中使用 Composition API:

// 不要混用!
export default {
  data() { return { count: 0 } },
  setup() {
    // 这可以工作但令人困惑
  }
}
Code

最佳实践和常见模式

1. 默认使用 <script setup>

<script setup>
// 简洁、清晰、高效
import { ref } from 'vue'
const count = ref(0)
</script>
Code

2. 使用 use 前缀命名组合式函数

// 好
useCounter()
useFetch()
useUser()

// 不好
getCounter()
fetchData()
userLogic()
Code

3. 保持组合式函数小巧专注

// 好 - 单一职责
export function useCounter() { }
export function useFetch() { }
export function useAuth() { }

// 不好 - 太多关注点
export function useEverything() { 
  // 计数器逻辑
  // 获取逻辑
  // 认证逻辑
}
Code

4. 从组合式函数返回显式对象

// 好 - 清晰的 API
export function useUser() {
  // ...
  return {
    user,
    loading,
    error,
    fetchUser,
    updateUser
  }
}

// 不好 - 不清楚返回什么
export function useUser() {
  // ...
  return { user, loading, error, fetchUser, updateUser, ...其他东西 }
}
Code

5. 对公共 API 使用 readonly()

import { ref, readonly } from 'vue'

export function useCounter() {
  const count = ref(0)

  const increment = () => count.value++

  // 暴露只读的 count
  return {
    count: readonly(count),
    increment
  }
}
Code

6. 一致地处理异步状态

// 标准模式
export function useFetch(url) {
  const data = ref(null)
  const loading = ref(true)
  const error = ref(null)

  // 获取逻辑...

  return { data, loading, error }
}
Code

7. 记录组合式函数选项

/**
 * @param {string} url - API 端点
 * @param {Object} options - 获取选项
 * @param {boolean} options.immediate - 挂载时获取
 * @returns {Object} data, loading, error, refetch
 */
export function useFetch(url, options = {}) {
  // ...
}
Code

结论

Vue 3 Composition API 代表了我们编写 Vue 组件方式的根本性转变。它提供:

  • ✅ 通过逻辑关注点更好地组织代码
  • ✅ 使用组合式函数更容易地重用逻辑
  • ✅ 一流的 TypeScript 支持
  • ✅ 更灵活的组件模式

关键要点: 1. 默认使用 <script setup> 以获得更简洁的语法 2. 使用组合式函数组织代码 3. 一致使用 ref() 处理响应式状态 4. 拥抱 TypeScript 以获得更好的开发体验 5. 遵循命名约定(use 前缀)

下一步: - 将一个组件重构为 Composition API - 创建你的第一个组合式函数 - 探索 Vue 3 生态系统(Pinia、Vue Router 4)

Composition API 不仅仅是新语法 —— 它是一种思考 Vue 组件的新方式。拥抱它,你的代码将更清晰、更易维护、更具可扩展性。


关于作者: 本文使用 AI 辅助编写,采用 AI 优先开发工作流。所有代码示例都已使用 Vue 3.4+ 测试。