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

Piper667
Piper667 2026-01-31T00:12:10+08:00
0 0 0

引言

Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。作为 Vue 3 的核心特性之一,Composition API 为开发者提供了一种更加灵活和强大的组件开发方式,特别是在处理复杂组件逻辑时表现尤为突出。

在 Vue 2 中,我们主要使用 Options API 来组织组件逻辑,这种方式虽然简单直观,但在面对复杂的组件时容易出现代码分散、难以维护的问题。而 Composition API 则通过将相关逻辑组合在一起的方式,让组件的结构更加清晰,同时也提供了更好的逻辑复用能力。

本文将从基础语法开始,逐步深入到复杂组件的状态管理、生命周期钩子、响应式编程等高级特性,帮助开发者全面掌握 Vue 3 Composition API 的使用方法。

什么是 Composition API

基本概念

Composition API 是 Vue 3 中引入的一种新的组件逻辑组织方式。它允许我们通过组合函数的方式来组织和复用组件逻辑,而不是传统的选项式 API(Options API)。

在传统的 Options API 中,组件的逻辑被分散到不同的选项中:datamethodscomputedwatch 等。而 Composition API 则将相关的逻辑集中在一起,使得代码更加模块化和可维护。

与 Options API 的对比

为了更好地理解 Composition API 的优势,我们先来看一个简单的对比示例:

Options API 写法:

export default {
  data() {
    return {
      count: 0,
      name: 'Vue'
    }
  },
  computed: {
    message() {
      return `Hello ${this.name}!`
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  watch: {
    count(newVal, oldVal) {
      console.log(`count changed from ${oldVal} to ${newVal}`)
    }
  }
}

Composition API 写法:

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

export default {
  setup() {
    const count = ref(0)
    const name = ref('Vue')
    
    const message = computed(() => `Hello ${name.value}!`)
    
    const increment = () => {
      count.value++
    }
    
    watch(count, (newVal, oldVal) => {
      console.log(`count changed from ${oldVal} to ${newVal}`)
    })
    
    return {
      count,
      name,
      message,
      increment
    }
  }
}

从上面的对比可以看出,Composition API 将相关的逻辑组织在一起,代码更加清晰易读。

Composition API 基础语法

setup 函数

setup 是 Composition API 的入口函数,它在组件实例创建之前执行。所有的 Composition API 函数都必须在 setup 函数中调用。

import { ref, reactive } from 'vue'

export default {
  setup() {
    // 在这里使用 Composition API
    const count = ref(0)
    const user = reactive({
      name: 'John',
      age: 25
    })
    
    return {
      count,
      user
    }
  }
}

响应式数据:ref 和 reactive

在 Composition API 中,我们使用 refreactive 来创建响应式数据。

ref 的使用

ref 用于创建基本类型的响应式数据:

import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const name = ref('Vue')
    const isActive = ref(true)
    
    // 访问值时需要使用 .value
    console.log(count.value) // 0
    
    // 修改值
    count.value = 10
    
    return {
      count,
      name,
      isActive
    }
  }
}

reactive 的使用

reactive 用于创建对象类型的响应式数据:

import { reactive } from 'vue'

export default {
  setup() {
    const user = reactive({
      name: 'John',
      age: 25,
      address: {
        city: 'Beijing',
        country: 'China'
      }
    })
    
    // 修改属性
    user.name = 'Jane'
    user.address.city = 'Shanghai'
    
    return {
      user
    }
  }
}

computed 和 watch

computed 的使用

computed 用于创建计算属性:

import { ref, computed } from 'vue'

export default {
  setup() {
    const firstName = ref('John')
    const lastName = ref('Doe')
    
    // 基本用法
    const fullName = computed(() => {
      return `${firstName.value} ${lastName.value}`
    })
    
    // 带 getter 和 setter 的计算属性
    const reversedName = computed({
      get: () => {
        return firstName.value.split('').reverse().join('')
      },
      set: (newValue) => {
        firstName.value = newValue.split('').reverse().join('')
      }
    })
    
    return {
      firstName,
      lastName,
      fullName,
      reversedName
    }
  }
}

watch 的使用

watch 用于监听响应式数据的变化:

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

export default {
  setup() {
    const count = ref(0)
    const name = ref('Vue')
    
    // 基本监听
    watch(count, (newVal, oldVal) => {
      console.log(`count changed from ${oldVal} to ${newVal}`)
    })
    
    // 监听多个源
    watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
      console.log(`count: ${oldCount} -> ${newCount}, name: ${oldName} -> ${newName}`)
    })
    
    // 深度监听
    const user = reactive({
      profile: {
        name: 'John',
        age: 25
      }
    })
    
    watch(user, (newVal, oldVal) => {
      console.log('user changed:', newVal)
    }, { deep: true })
    
    // 立即执行的监听器
    watch(count, (newVal) => {
      console.log(`count is now ${newVal}`)
    }, { immediate: true })
    
    return {
      count,
      name
    }
  }
}

watchEffect

watchEffect 是一个更简洁的监听函数,它会自动追踪其内部使用的响应式数据:

import { ref, watchEffect } from 'vue'

export default {
  setup() {
    const count = ref(0)
    
    // watchEffect 会立即执行,并自动追踪依赖
    const stop = watchEffect(() => {
      console.log(`count is: ${count.value}`)
    })
    
    // 停止监听
    // stop()
    
    return {
      count
    }
  }
}

组件生命周期钩子

生命周期钩子函数

Composition API 提供了与 Vue 2 相同的生命周期钩子,但以函数的形式提供:

import { 
  onMounted, 
  onUpdated, 
  onUnmounted,
  onBeforeMount,
  onBeforeUpdate,
  onBeforeUnmount
} from 'vue'

export default {
  setup() {
    // 组件挂载时调用
    onMounted(() => {
      console.log('Component mounted')
    })
    
    // 组件更新时调用
    onUpdated(() => {
      console.log('Component updated')
    })
    
    // 组件卸载前调用
    onBeforeUnmount(() => {
      console.log('Component will unmount')
    })
    
    return {}
  }
}

实际应用示例

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

export default {
  setup() {
    const timer = ref(null)
    const count = ref(0)
    
    // 模拟定时器功能
    onMounted(() => {
      timer.value = setInterval(() => {
        count.value++
      }, 1000)
    })
    
    onUnmounted(() => {
      if (timer.value) {
        clearInterval(timer.value)
      }
    })
    
    return {
      count
    }
  }
}

复杂组件状态管理

状态管理的最佳实践

在复杂的组件中,合理的状态管理至关重要。我们可以通过组合多个响应式数据来构建复杂的状态:

import { ref, reactive, computed } from 'vue'

export default {
  setup() {
    // 用户相关状态
    const user = reactive({
      id: null,
      name: '',
      email: '',
      avatar: ''
    })
    
    // 加载状态
    const loading = ref(false)
    
    // 错误状态
    const error = ref(null)
    
    // 表单数据
    const formData = reactive({
      name: '',
      email: '',
      password: ''
    })
    
    // 计算属性
    const isValidForm = computed(() => {
      return formData.name && formData.email && formData.password
    })
    
    const isUserLoggedIn = computed(() => {
      return !!user.id
    })
    
    // 方法
    const login = async () => {
      loading.value = true
      error.value = null
      
      try {
        // 模拟 API 调用
        await new Promise(resolve => setTimeout(resolve, 1000))
        
        user.id = 1
        user.name = formData.name
        user.email = formData.email
        
        console.log('Login successful')
      } catch (err) {
        error.value = 'Login failed'
        console.error(err)
      } finally {
        loading.value = false
      }
    }
    
    const logout = () => {
      user.id = null
      user.name = ''
      user.email = ''
      user.avatar = ''
      
      formData.name = ''
      formData.email = ''
      formData.password = ''
    }
    
    return {
      user,
      loading,
      error,
      formData,
      isValidForm,
      isUserLoggedIn,
      login,
      logout
    }
  }
}

多层级状态管理

对于更加复杂的组件,我们可以将状态组织成多个模块:

import { ref, reactive, computed } from 'vue'

export default {
  setup() {
    // 订单状态
    const orderState = reactive({
      items: [],
      total: 0,
      status: 'pending'
    })
    
    // 用户偏好设置
    const preferences = reactive({
      theme: 'light',
      notifications: true,
      language: 'zh-CN'
    })
    
    // 加载状态
    const loadingStates = reactive({
      orderList: false,
      orderDetails: false,
      cart: false
    })
    
    // 计算属性
    const cartTotal = computed(() => {
      return orderState.items.reduce((total, item) => {
        return total + (item.price * item.quantity)
      }, 0)
    })
    
    const hasItemsInCart = computed(() => {
      return orderState.items.length > 0
    })
    
    // 方法
    const addToCart = (product) => {
      const existingItem = orderState.items.find(item => item.id === product.id)
      
      if (existingItem) {
        existingItem.quantity += 1
      } else {
        orderState.items.push({
          id: product.id,
          name: product.name,
          price: product.price,
          quantity: 1
        })
      }
    }
    
    const removeFromCart = (productId) => {
      orderState.items = orderState.items.filter(item => item.id !== productId)
    }
    
    const updateQuantity = (productId, newQuantity) => {
      const item = orderState.items.find(item => item.id === productId)
      if (item) {
        item.quantity = Math.max(0, newQuantity)
        if (item.quantity === 0) {
          removeFromCart(productId)
        }
      }
    }
    
    return {
      orderState,
      preferences,
      loadingStates,
      cartTotal,
      hasItemsInCart,
      addToCart,
      removeFromCart,
      updateQuantity
    }
  }
}

组件间通信

父子组件通信

在 Composition API 中,父子组件通信依然可以使用传统的 props 和 emit 方式:

// 父组件
import { ref } from 'vue'

export default {
  setup() {
    const message = ref('Hello from parent')
    
    const handleChildEvent = (data) => {
      console.log('Received from child:', data)
    }
    
    return {
      message,
      handleChildEvent
    }
  }
}
<!-- 父组件模板 -->
<template>
  <child-component 
    :message="message"
    @child-event="handleChildEvent"
  />
</template>
// 子组件
export default {
  props: {
    message: String
  },
  emits: ['child-event'],
  setup(props, { emit }) {
    const handleClick = () => {
      emit('child-event', 'Hello from child')
    }
    
    return {
      handleClick
    }
  }
}

非父子组件通信

对于非父子组件间的通信,我们可以使用全局状态管理:

// store.js
import { reactive } from 'vue'

export const useGlobalStore = () => {
  const state = reactive({
    theme: 'light',
    language: 'zh-CN'
  })
  
  const setTheme = (theme) => {
    state.theme = theme
  }
  
  const setLanguage = (language) => {
    state.language = language
  }
  
  return {
    state,
    setTheme,
    setLanguage
  }
}
// 在组件中使用
import { useGlobalStore } from './store'

export default {
  setup() {
    const { state, setTheme } = useGlobalStore()
    
    const changeTheme = () => {
      setTheme(state.theme === 'light' ? 'dark' : 'light')
    }
    
    return {
      state,
      changeTheme
    }
  }
}

高级特性与最佳实践

自定义组合函数

自定义组合函数是 Composition API 的一大优势,它可以帮助我们复用逻辑:

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

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  
  const increment = () => {
    count.value++
  }
  
  const decrement = () => {
    count.value--
  }
  
  const reset = () => {
    count.value = initialValue
  }
  
  const doubleCount = computed(() => count.value * 2)
  
  return {
    count,
    increment,
    decrement,
    reset,
    doubleCount
  }
}
// 在组件中使用
import { useCounter } from '@/composables/useCounter'

export default {
  setup() {
    const { 
      count, 
      increment, 
      decrement, 
      reset,
      doubleCount
    } = useCounter(10)
    
    return {
      count,
      increment,
      decrement,
      reset,
      doubleCount
    }
  }
}

异步数据获取

处理异步数据是现代前端开发中的常见需求:

import { ref, onMounted } from 'vue'

export default {
  setup() {
    const data = ref(null)
    const loading = ref(false)
    const error = ref(null)
    
    const fetchData = async () => {
      loading.value = true
      error.value = null
      
      try {
        // 模拟 API 调用
        const response = await fetch('/api/data')
        data.value = await response.json()
      } catch (err) {
        error.value = err.message
        console.error('Failed to fetch data:', err)
      } finally {
        loading.value = false
      }
    }
    
    onMounted(() => {
      fetchData()
    })
    
    return {
      data,
      loading,
      error,
      fetchData
    }
  }
}

性能优化

在使用 Composition API 时,需要注意性能优化:

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

export default {
  setup() {
    const count = ref(0)
    
    // 使用计算属性缓存复杂计算
    const expensiveValue = computed(() => {
      // 模拟复杂的计算
      let result = 0
      for (let i = 0; i < 1000000; i++) {
        result += Math.sqrt(i)
      }
      return result + count.value
    })
    
    // 监听器的优化
    const debouncedWatch = (source, cb, delay = 300) => {
      let timeout
      return watch(source, (...args) => {
        clearTimeout(timeout)
        timeout = setTimeout(() => cb(...args), delay)
      })
    }
    
    // 使用防抖监听器
    debouncedWatch(count, (newVal) => {
      console.log('Count changed:', newVal)
    })
    
    return {
      count,
      expensiveValue
    }
  }
}

实际项目应用案例

用户管理组件示例

让我们来看一个完整的用户管理组件的实际应用:

<template>
  <div class="user-management">
    <div class="header">
      <h2>用户管理</h2>
      <button @click="showCreateForm = true">添加用户</button>
    </div>
    
    <!-- 用户列表 -->
    <div v-if="!showCreateForm" class="user-list">
      <div 
        v-for="user in paginatedUsers" 
        :key="user.id"
        class="user-card"
      >
        <div class="user-info">
          <h3>{{ user.name }}</h3>
          <p>{{ user.email }}</p>
        </div>
        <div class="user-actions">
          <button @click="editUser(user)">编辑</button>
          <button @click="deleteUser(user.id)">删除</button>
        </div>
      </div>
      
      <!-- 分页 -->
      <div class="pagination">
        <button 
          @click="currentPage--" 
          :disabled="currentPage === 1"
        >
          上一页
        </button>
        <span>{{ currentPage }} / {{ totalPages }}</span>
        <button 
          @click="currentPage++" 
          :disabled="currentPage === totalPages"
        >
          下一页
        </button>
      </div>
    </div>
    
    <!-- 创建/编辑表单 -->
    <div v-else class="user-form">
      <h3>{{ editingUser ? '编辑用户' : '添加用户' }}</h3>
      <form @submit.prevent="saveUser">
        <input 
          v-model="formData.name" 
          placeholder="姓名"
          required
        />
        <input 
          v-model="formData.email" 
          type="email"
          placeholder="邮箱"
          required
        />
        <input 
          v-model="formData.phone" 
          placeholder="电话"
        />
        <button type="submit">保存</button>
        <button type="button" @click="showCreateForm = false">取消</button>
      </form>
    </div>
  </div>
</template>

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

export default {
  name: 'UserManagement',
  setup() {
    // 状态管理
    const users = ref([])
    const loading = ref(false)
    const error = ref(null)
    
    // 表单状态
    const showCreateForm = ref(false)
    const editingUser = ref(null)
    
    // 分页状态
    const currentPage = ref(1)
    const pageSize = ref(10)
    
    // 表单数据
    const formData = reactive({
      name: '',
      email: '',
      phone: ''
    })
    
    // 计算属性
    const totalPages = computed(() => {
      return Math.ceil(users.value.length / pageSize.value)
    })
    
    const paginatedUsers = computed(() => {
      const start = (currentPage.value - 1) * pageSize.value
      const end = start + pageSize.value
      return users.value.slice(start, end)
    })
    
    // 方法
    const fetchUsers = async () => {
      loading.value = true
      error.value = null
      
      try {
        // 模拟 API 调用
        await new Promise(resolve => setTimeout(resolve, 1000))
        
        users.value = [
          { id: 1, name: '张三', email: 'zhangsan@example.com', phone: '13800138000' },
          { id: 2, name: '李四', email: 'lisi@example.com', phone: '13800138001' },
          { id: 3, name: '王五', email: 'wangwu@example.com', phone: '13800138002' }
        ]
      } catch (err) {
        error.value = '获取用户列表失败'
        console.error(err)
      } finally {
        loading.value = false
      }
    }
    
    const editUser = (user) => {
      editingUser.value = user
      Object.assign(formData, user)
      showCreateForm.value = true
    }
    
    const deleteUser = async (userId) => {
      if (confirm('确定要删除这个用户吗?')) {
        try {
          // 模拟 API 调用
          await new Promise(resolve => setTimeout(resolve, 500))
          
          users.value = users.value.filter(user => user.id !== userId)
          console.log(`用户 ${userId} 已删除`)
        } catch (err) {
          error.value = '删除用户失败'
          console.error(err)
        }
      }
    }
    
    const saveUser = async () => {
      loading.value = true
      
      try {
        if (editingUser.value) {
          // 更新用户
          const index = users.value.findIndex(user => user.id === editingUser.value.id)
          if (index !== -1) {
            users.value[index] = { ...users.value[index], ...formData }
          }
        } else {
          // 创建新用户
          const newUser = {
            id: Date.now(),
            ...formData
          }
          users.value.push(newUser)
        }
        
        resetForm()
        console.log('用户保存成功')
      } catch (err) {
        error.value = '保存用户失败'
        console.error(err)
      } finally {
        loading.value = false
      }
    }
    
    const resetForm = () => {
      showCreateForm.value = false
      editingUser.value = null
      Object.assign(formData, { name: '', email: '', phone: '' })
    }
    
    // 监听分页变化
    watch(currentPage, () => {
      console.log(`切换到第 ${currentPage.value} 页`)
    })
    
    // 组件挂载时获取数据
    onMounted(() => {
      fetchUsers()
    })
    
    return {
      users,
      loading,
      error,
      showCreateForm,
      editingUser,
      currentPage,
      pageSize,
      totalPages,
      paginatedUsers,
      formData,
      fetchUsers,
      editUser,
      deleteUser,
      saveUser,
      resetForm
    }
  }
}
</script>

<style scoped>
.user-management {
  padding: 20px;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.user-list {
  margin-bottom: 20px;
}

.user-card {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 15px;
  border: 1px solid #ddd;
  margin-bottom: 10px;
  border-radius: 4px;
}

.user-info h3 {
  margin: 0 0 5px 0;
}

.user-info p {
  margin: 0;
  color: #666;
}

.user-actions button {
  margin-left: 10px;
  padding: 5px 10px;
  border: none;
  border-radius: 3px;
  cursor: pointer;
}

.pagination {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 10px;
  margin-top: 20px;
}

.user-form {
  padding: 20px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.user-form form {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.user-form input {
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 3px;
}

.user-form button {
  padding: 10px;
  border: none;
  border-radius: 3px;
  cursor: pointer;
}
</style>

总结

Vue 3 Composition API 的引入为前端开发带来了革命性的变化。通过本文的详细介绍,我们看到了 Composition API 在以下几个方面的重要优势:

主要优势

  1. 更好的逻辑组织:将相关的逻辑集中在一起,避免了 Options API 中代码分散的问题
  2. 更强的复用能力:通过自定义组合函数,可以轻松地在不同组件间复用逻辑
  3. 更灵活的开发方式:开发者可以根据需要自由组织组件逻辑
  4. 更好的 TypeScript 支持:Composition API 与 TypeScript 的集成更加自然

最佳实践建议

  1. 合理使用响应式数据:根据数据类型选择 refreactive
  2. 善用计算属性:利用 computed 进行复杂计算的缓存
  3. 正确的生命周期管理:合理使用生命周期钩子函数
  4. 组件化思维:将复杂逻辑拆分成多个可复用的组合函数

未来展望

随着 Vue 生态系统的不断发展,Composition API 必将成为现代 Vue 开发的标准。它不仅适用于简单的组件开发,更在大型应用的状态管理和逻辑复用方面展现出巨大优势。

掌握 Composition API 不仅能帮助我们编写更加清晰、可维护的代码,还能让我们更好地适应前端技术的发展趋势。建议开发者在项目中积极尝试和应用 Composition API,逐步将其融入到日常开发工作中。

通过本文的学习,相信读者已经对 Vue 3 Composition API 有了全面深入的理解,并能够在实际项目中灵活运用这些知识来构建高质量的前端应用。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000