← 返回首页

Vue 3 Composition API 实战指南

发布于 2025-01-02 | 作者:水码 | 阅读约 10 分钟

Vue 3 的 Composition API 彻底改变了我们组织 Vue 组件代码的方式。相比 Options API,它提供了更灵活的逻辑复用和更好的 TypeScript 支持。本文将通过实际案例,带你深入理解 Composition API 的核心概念与最佳实践。

一、为什么需要 Composition API?

Options API 在处理复杂组件时存在明显痛点:

  • 逻辑分散:同一功能的代码被拆分到 data、methods、computed、watch 等选项中;
  • 复用困难:Mixins 存在命名冲突、来源不清晰等问题;
  • TypeScript 支持差:this 上下文推断复杂。

Composition API 允许我们按功能组织代码,并通过组合函数(Composables)实现逻辑复用。

二、核心 API 详解

1. ref 与 reactive

这是响应式数据的两种创建方式:

import { ref, reactive } from 'vue'

// ref:适用于基本类型,访问需要 .value
const count = ref(0)
console.log(count.value) // 0
count.value++

// reactive:适用于对象,直接访问属性
const state = reactive({
  name: '水码',
  age: 25
})
console.log(state.name) // '水码'
state.age = 26

💡 建议:优先使用 ref,因为它在解构、传递时不会丢失响应性。

2. computed 计算属性

import { ref, computed } from 'vue'

const firstName = ref('张')
const lastName = ref('三')

// 只读计算属性
const fullName = computed(() => firstName.value + lastName.value)

// 可写计算属性
const fullNameWritable = computed({
  get: () => firstName.value + lastName.value,
  set: (val) => {
    firstName.value = val[0]
    lastName.value = val.slice(1)
  }
})

3. watch 与 watchEffect

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

const keyword = ref('')

// watch:显式指定监听源
watch(keyword, (newVal, oldVal) => {
  console.log(`搜索词从 ${oldVal} 变为 ${newVal}`)
}, { immediate: true })

// watchEffect:自动收集依赖
watchEffect(() => {
  console.log(`当前搜索词:${keyword.value}`)
  // 自动追踪 keyword.value
})

4. 生命周期钩子

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

onMounted(() => {
  console.log('组件已挂载')
  window.addEventListener('resize', handleResize)
})

onUnmounted(() => {
  console.log('组件将卸载')
  window.removeEventListener('resize', handleResize)
})

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

三、组合函数(Composables)实战

这是 Composition API 最强大的特性——将逻辑封装为可复用的函数:

示例:useMouse 鼠标位置追踪

// composables/useMouse.ts
import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  function update(e: MouseEvent) {
    x.value = e.pageX
    y.value = e.pageY
  }

  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  return { x, y }
}
// 在组件中使用
<script setup>
import { useMouse } from '@/composables/useMouse'

const { x, y } = useMouse()
</script>

<template>
  <p>鼠标位置:{{ x }}, {{ y }}</p>
</template>

示例:useFetch 数据请求

// composables/useFetch.ts
import { ref, watchEffect, toValue } from 'vue'

export function useFetch<T>(url: string | Ref<string>) {
  const data = ref<T | null>(null)
  const error = ref<Error | null>(null)
  const loading = ref(false)

  async function fetchData() {
    loading.value = true
    error.value = null
    
    try {
      const res = await fetch(toValue(url))
      data.value = await res.json()
    } catch (e) {
      error.value = e as Error
    } finally {
      loading.value = false
    }
  }

  watchEffect(() => {
    fetchData()
  })

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

四、<script setup> 语法糖

Vue 3.2 引入的 <script setup> 是 Composition API 的编译时语法糖,大幅简化代码:

<script setup lang="ts">
import { ref, computed } from 'vue'
import { useMouse } from '@/composables/useMouse'

// 直接声明响应式变量
const count = ref(0)
const double = computed(() => count.value * 2)

// 组合函数
const { x, y } = useMouse()

// 定义 props 和 emits
const props = defineProps<{
  title: string
  count?: number
}>()

const emit = defineEmits<{
  (e: 'update', value: number): void
}>()

// 函数直接可用于模板
function increment() {
  count.value++
  emit('update', count.value)
}
</script>

<template>
  <h1>{{ title }}</h1>
  <button @click="increment">{{ count }} × 2 = {{ double }}</button>
</template>

五、最佳实践

  1. 按功能组织代码:相关逻辑放在一起,而非按选项类型分散;
  2. 命名规范:组合函数以 use 开头,如 useAuthuseCart
  3. 保持纯粹:组合函数应专注单一职责,避免副作用耦合;
  4. 善用 TypeScript:充分利用类型推断和泛型;
  5. 避免过度抽象:不要为复用而复用,简单逻辑留在组件内即可。

六、学习资源

Composition API 不仅是语法的升级,更是思维方式的转变。掌握它,你将写出更清晰、更可维护、更易测试的 Vue 代码。现在就开始重构你的组件吧!