Vue 3 Composition API实战指南:从基础语法到复杂组件状态管理的完整教程

Trudy676
Trudy676 2026-02-04T17:17:10+08:00
0 0 1

引言

Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。相比 Vue 2 中的 Options API,Composition API 提供了更灵活、更强大的组件开发方式,特别适合处理复杂的业务逻辑和状态管理。本文将深入探讨 Composition API 的核心概念、使用方法以及最佳实践,帮助开发者从基础语法逐步掌握到构建复杂应用。

Vue 3 Composition API 核心概念

什么是 Composition API?

Composition API 是 Vue 3 中引入的一种新的组件开发方式,它允许我们通过组合函数来组织和复用组件逻辑。与传统的 Options API(data、methods、computed、watch 等选项)不同,Composition API 将组件的逻辑按照功能进行分组,使得代码更加模块化和可维护。

为什么需要 Composition API?

在 Vue 2 中,我们通常将组件逻辑分散在不同的选项中:

  • data 存储响应式数据
  • methods 定义方法
  • computed 处理计算属性
  • watch 监听数据变化

这种方式在小型组件中表现良好,但在大型复杂组件中会出现以下问题:

  1. 逻辑分散:相关功能被拆分到不同选项中,难以维护
  2. 代码复用困难:无法轻松在组件间共享逻辑
  3. 类型推断困难:IDE 智能提示不够完善

Composition API 通过函数式的方式重新组织代码结构,解决了这些问题。

响应式系统详解

reactive 和 ref 的基础使用

Vue 3 提供了两个核心的响应式 API:reactiveref

import { reactive, ref } from 'vue'

// 使用 ref 创建响应式数据
const count = ref(0)
const message = ref('Hello Vue')

// 使用 reactive 创建响应式对象
const state = reactive({
  name: 'John',
  age: 25,
  hobbies: ['reading', 'coding']
})

// 访问和修改值
console.log(count.value) // 0
count.value = 10

console.log(state.name) // John
state.name = 'Jane'

ref 的深层理解

ref 是 Vue 3 响应式系统的核心,它会自动包装基本类型数据:

import { ref, watchEffect } from 'vue'

const count = ref(0)
const obj = ref({ name: 'Alice' })

// 监听 ref 变化
watchEffect(() => {
  console.log(`count is ${count.value}`)
})

// 修改值会触发监听器
count.value++ // 输出: count is 1

// 对于对象类型的 ref,修改属性也会触发响应
obj.value.name = 'Bob' // 这个变化同样会被监听到

reactive 的使用场景

reactive 更适合处理复杂对象结构:

import { reactive, computed } from 'vue'

const user = reactive({
  profile: {
    name: 'John',
    email: 'john@example.com'
  },
  settings: {
    theme: 'dark',
    language: 'en'
  }
})

// 计算属性
const displayName = computed(() => {
  return user.profile.name.toUpperCase()
})

// 直接修改嵌套对象
user.profile.name = 'Jane'
console.log(displayName.value) // JANE

组合式函数的创建与使用

创建组合式函数

组合式函数是封装和复用逻辑的核心机制:

import { ref, watch } from 'vue'

// 计数器组合式函数
export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  
  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = initialValue
  
  return {
    count,
    increment,
    decrement,
    reset
  }
}

// 用户信息组合式函数
export function useUser() {
  const user = ref(null)
  const loading = ref(false)
  
  const fetchUser = async (userId) => {
    loading.value = true
    try {
      const response = await fetch(`/api/users/${userId}`)
      user.value = await response.json()
    } catch (error) {
      console.error('Failed to fetch user:', error)
    } finally {
      loading.value = false
    }
  }
  
  return {
    user,
    loading,
    fetchUser
  }
}

使用组合式函数

import { defineComponent } from 'vue'
import { useCounter, useUser } from './composables'

export default defineComponent({
  setup() {
    // 使用计数器组合式函数
    const counter = useCounter(10)
    
    // 使用用户信息组合式函数
    const user = useUser()
    
    return {
      ...counter,
      ...user
    }
  }
})

组件通信机制

父子组件通信

<!-- Parent.vue -->
<template>
  <div>
    <h2>Parent Component</h2>
    <Child 
      :message="parentMessage"
      :count="counter"
      @child-event="handleChildEvent"
    />
    <button @click="increment">Increment: {{ counter }}</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const parentMessage = ref('Hello from Parent')
const counter = ref(0)

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

const handleChildEvent = (data) => {
  console.log('Received from child:', data)
}
</script>
<!-- Child.vue -->
<template>
  <div>
    <h3>Child Component</h3>
    <p>{{ message }}</p>
    <p>Count: {{ count }}</p>
    <button @click="sendToParent">Send to Parent</button>
  </div>
</template>

<script setup>
import { emit } from 'vue'

defineProps({
  message: String,
  count: Number
})

const emit = defineEmits(['child-event'])

const sendToParent = () => {
  emit('child-event', {
    timestamp: Date.now(),
    data: 'Hello from child'
  })
}
</script>

provide/inject 通信

<!-- Parent.vue -->
<template>
  <div>
    <h2>Provider Component</h2>
    <Child />
  </div>
</template>

<script setup>
import { provide, ref } from 'vue'
import Child from './Child.vue'

const theme = ref('dark')
const user = ref({ name: 'John', role: 'admin' })

provide('theme', theme)
provide('user', user)
provide('updateTheme', (newTheme) => {
  theme.value = newTheme
})
</script>
<!-- Child.vue -->
<template>
  <div :class="`theme-${theme}`">
    <h3>User: {{ user.name }}</h3>
    <button @click="changeTheme">Change Theme</button>
  </div>
</template>

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

const theme = inject('theme')
const user = inject('user')
const updateTheme = inject('updateTheme')

const changeTheme = () => {
  const newTheme = theme.value === 'dark' ? 'light' : 'dark'
  updateTheme(newTheme)
}
</script>

复杂状态管理实践

创建全局状态管理器

// stores/userStore.js
import { reactive, readonly } from 'vue'

const state = reactive({
  currentUser: null,
  isLoggedIn: false,
  loading: false,
  error: null
})

const actions = {
  async login(credentials) {
    state.loading = true
    state.error = null
    
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(credentials)
      })
      
      if (!response.ok) {
        throw new Error('Login failed')
      }
      
      const userData = await response.json()
      state.currentUser = userData
      state.isLoggedIn = true
    } catch (error) {
      state.error = error.message
      console.error('Login error:', error)
    } finally {
      state.loading = false
    }
  },
  
  logout() {
    state.currentUser = null
    state.isLoggedIn = false
  }
}

export const useUserStore = () => {
  return {
    state: readonly(state),
    ...actions
  }
}

使用状态管理器

<!-- Login.vue -->
<template>
  <div class="login-form">
    <form @submit.prevent="handleLogin">
      <input 
        v-model="credentials.username" 
        placeholder="Username" 
        required 
      />
      <input 
        v-model="credentials.password" 
        type="password" 
        placeholder="Password" 
        required 
      />
      <button type="submit" :disabled="loading">Login</button>
      <div v-if="error" class="error">{{ error }}</div>
    </form>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'
import { useUserStore } from '../stores/userStore'

const { state: userState, login } = useUserStore()
const credentials = reactive({
  username: '',
  password: ''
})

const handleLogin = async () => {
  await login(credentials)
  
  if (!userState.error) {
    // 登录成功后的处理
    console.log('Login successful')
  }
}

// 访问状态
const loading = computed(() => userState.loading)
const error = computed(() => userState.error)
</script>

高级组合式函数模式

带有副作用的组合式函数

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

export function useFetch(url, options = {}) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)
  
  const fetchData = async () => {
    if (!url) return
    
    loading.value = true
    error.value = null
    
    try {
      const response = await fetch(url, options)
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      data.value = await response.json()
    } catch (err) {
      error.value = err.message
      console.error('Fetch error:', err)
    } finally {
      loading.value = false
    }
  }
  
  // 自动执行
  if (options.autoFetch !== false) {
    fetchData()
  }
  
  // 监听 url 变化,重新获取数据
  watch(url, fetchData, { immediate: true })
  
  return {
    data,
    loading,
    error,
    refetch: fetchData
  }
}

使用 fetch 组合式函数

<template>
  <div>
    <div v-if="loading">Loading...</div>
    <div v-else-if="error">{{ error }}</div>
    <div v-else>
      <h2>{{ data?.title }}</h2>
      <p>{{ data?.content }}</p>
      <button @click="refetch">Refresh</button>
    </div>
  </div>
</template>

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

const articleId = ref(1)
const { data, loading, error, refetch } = useFetch(
  () => `/api/articles/${articleId.value}`,
  { autoFetch: false }
)

// 当文章ID变化时重新获取数据
const updateArticle = (id) => {
  articleId.value = id
}
</script>

性能优化策略

使用 computed 和 watch 的最佳实践

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

export default {
  setup() {
    const firstName = ref('John')
    const lastName = ref('Doe')
    
    // 计算属性 - 高效的缓存机制
    const fullName = computed(() => {
      return `${firstName.value} ${lastName.value}`
    })
    
    // 监听器 - 只在依赖变化时执行
    watch(firstName, (newVal, oldVal) => {
      console.log(`First name changed from ${oldVal} to ${newVal}`)
    })
    
    // watchEffect - 自动追踪依赖
    watchEffect(() => {
      console.log(`Full name is: ${fullName.value}`)
      // 这里会自动追踪 fullName 的变化
    })
    
    return {
      firstName,
      lastName,
      fullName
    }
  }
}

避免不必要的重新渲染

<template>
  <div>
    <button @click="increment">Count: {{ count }}</button>
    <button @click="updateName">Update Name</button>
    <!-- 使用 v-memo 优化复杂计算 -->
    <div>{{ expensiveCalculation }}</div>
  </div>
</template>

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

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

// 使用 computed 缓存计算结果
const expensiveCalculation = computed(() => {
  // 模拟复杂的计算
  return Array.from({ length: 1000 }, (_, i) => i * 2).reduce((sum, val) => sum + val, 0)
})

const increment = () => count.value++
const updateName = () => name.value = 'Jane'
</script>

实际项目应用案例

创建一个完整的购物车组件

<!-- ShoppingCart.vue -->
<template>
  <div class="shopping-cart">
    <h2>Shopping Cart</h2>
    
    <!-- 购物车列表 -->
    <div v-if="cartItems.length > 0" class="cart-items">
      <div 
        v-for="item in cartItems" 
        :key="item.id"
        class="cart-item"
      >
        <span>{{ item.name }}</span>
        <span>Quantity: {{ item.quantity }}</span>
        <span>Price: ${{ item.price }}</span>
        <button @click="removeItem(item.id)">Remove</button>
      </div>
    </div>
    
    <!-- 购物车统计 -->
    <div class="cart-summary">
      <p>Total Items: {{ totalItems }}</p>
      <p>Total Price: ${{ totalPrice }}</p>
      <button @click="clearCart" :disabled="cartItems.length === 0">
        Clear Cart
      </button>
    </div>
    
    <!-- 加载状态 -->
    <div v-if="loading">Loading cart...</div>
    <div v-else-if="error">{{ error }}</div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import { useFetch } from '../composables/useFetch'

// 购物车状态管理
const cartItems = ref([])
const loading = ref(false)
const error = ref(null)

// 获取购物车数据
const fetchCart = async () => {
  try {
    loading.value = true
    const response = await fetch('/api/cart')
    if (!response.ok) throw new Error('Failed to fetch cart')
    cartItems.value = await response.json()
  } catch (err) {
    error.value = err.message
  } finally {
    loading.value = false
  }
}

// 计算属性
const totalItems = computed(() => {
  return cartItems.value.reduce((sum, item) => sum + item.quantity, 0)
})

const totalPrice = computed(() => {
  return cartItems.value.reduce((sum, item) => 
    sum + (item.price * item.quantity), 0)
  )
})

// 操作方法
const removeItem = (itemId) => {
  cartItems.value = cartItems.value.filter(item => item.id !== itemId)
}

const clearCart = () => {
  cartItems.value = []
}

// 组件挂载时获取数据
fetchCart()
</script>

<style scoped>
.shopping-cart {
  padding: 20px;
}

.cart-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
  border-bottom: 1px solid #eee;
}

.cart-summary {
  margin-top: 20px;
  padding: 20px;
  background-color: #f5f5f5;
}
</style>

最佳实践总结

代码组织原则

// 推荐的目录结构
src/
├── composables/           # 组合式函数
│   ├── useCounter.js
│   ├── useUser.js
│   └── useFetch.js
├── stores/                # 状态管理
│   ├── userStore.js
│   └── cartStore.js
├── components/            # 组件
│   ├── CartItem.vue
│   └── ProductList.vue
└── views/                 # 页面视图
    └── Home.vue

类型安全和开发体验

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

// TypeScript 类型定义
interface User {
  id: number
  name: string
  email: string
}

const user = ref<User | null>(null)
const loading = ref(false)

const displayName = computed(() => {
  return user.value?.name || 'Unknown'
})

const fetchUser = async (id: number) => {
  loading.value = true
  try {
    const response = await fetch(`/api/users/${id}`)
    user.value = await response.json()
  } catch (error) {
    console.error('Error fetching user:', error)
  } finally {
    loading.value = false
  }
}
</script>

总结

Vue 3 的 Composition API 为前端开发带来了革命性的变化,它不仅提供了更灵活的组件组织方式,还大大增强了代码的可复用性和可维护性。通过本文的详细讲解和实践案例,我们可以看到:

  1. 响应式系统refreactive 提供了强大的数据响应能力
  2. 组合式函数:通过函数封装逻辑,实现代码复用
  3. 状态管理:结合组合式函数可以构建复杂的全局状态管理
  4. 性能优化:合理使用计算属性和监听器,避免不必要的重新渲染

在实际项目中,建议遵循以下原则:

  • 将相关逻辑组织到组合式函数中
  • 合理使用 refreactive,根据数据结构选择合适的响应式方式
  • 重视类型安全,特别是在大型项目中
  • 善用计算属性和监听器来优化性能

随着 Vue 生态系统的不断发展,Composition API 必将成为现代 Vue 开发的标准实践。掌握这一技术不仅能够提升开发效率,还能帮助我们构建更加健壮和可维护的应用程序。

通过持续的实践和探索,我们可以充分发挥 Composition API 的潜力,创造出更优秀的 Vue 应用。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000