Vue 3 Composition API 最佳实践:从组件复用到状态管理的完整指南

代码与诗歌
代码与诗歌 2026-02-07T02:08:43+08:00
0 0 1

引言

Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。这一新特性不仅改变了我们编写 Vue 组件的方式,还为开发者提供了更灵活、更强大的开发模式。相比于 Vue 2 中的选项式 API,Composition API 更加注重逻辑复用和代码组织,让组件变得更加模块化和可维护。

在本文中,我们将深入探讨 Vue 3 Composition API 的核心概念和使用技巧,从基础的响应式数据管理到高级的组件逻辑复用,再到状态管理的最佳实践。通过详细的代码示例和实际应用场景,帮助开发者掌握这一现代前端开发的重要工具。

Composition API 核心概念

响应式数据管理

Composition API 的核心在于对响应式数据的管理。在 Vue 3 中,我们可以通过 refreactive 来创建响应式数据:

import { ref, reactive } from 'vue'

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

// 使用 reactive 创建响应式对象
const state = reactive({
  count: 0,
  name: 'Vue',
  user: {
    firstName: 'John',
    lastName: 'Doe'
  }
})

// 在模板中使用
// <template>
//   <p>{{ count }}</p>
//   <p>{{ name }}</p>
//   <p>{{ state.count }}</p>
// </template>

响应式数据的访问和修改

在 Composition API 中,访问响应式数据需要通过 .value 属性:

import { ref, reactive } from 'vue'

const count = ref(0)
const userInfo = reactive({
  name: 'John',
  age: 25
})

// 修改数据
count.value = 10
userInfo.name = 'Jane'
userInfo.age = 30

// 在模板中访问
// <template>
//   <p>{{ count }}</p>
//   <p>{{ userInfo.name }}</p>
// </template>

计算属性和监听器

Composition API 提供了 computedwatch 来处理计算属性和监听器:

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

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

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

// 监听器
const count = ref(0)

watch(count, (newValue, oldValue) => {
  console.log(`Count changed from ${oldValue} to ${newValue}`)
})

// 监听多个数据源
watch([firstName, lastName], ([newFirstName, newLastName], [oldFirstName, oldLastName]) => {
  console.log(`Name changed from ${oldFirstName} ${oldLastName} to ${newFirstName} ${newLastName}`)
})

组件逻辑复用

自定义组合式函数

组合式函数是 Vue 3 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
    }
  }
}

复杂逻辑的封装

让我们来看一个更复杂的例子,封装一个数据获取和加载状态管理的组合式函数:

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

export function useApi(apiFunction, initialData = null) {
  const data = ref(initialData)
  const loading = ref(false)
  const error = ref(null)
  
  const fetchData = async (...args) => {
    try {
      loading.value = true
      error.value = null
      const result = await apiFunction(...args)
      data.value = result
    } catch (err) {
      error.value = err
      console.error('API Error:', err)
    } finally {
      loading.value = false
    }
  }
  
  const hasData = computed(() => data.value !== null && data.value !== undefined)
  
  return {
    data,
    loading,
    error,
    fetchData,
    hasData
  }
}

// 在组件中使用
import { useApi } from '@/composables/useApi'

export default {
  setup() {
    const { data, loading, error, fetchData, hasData } = useApi(fetchUserData, null)
    
    // 初始化时获取数据
    fetchData('user/123')
    
    return {
      data,
      loading,
      error,
      fetchData,
      hasData
    }
  }
}

状态共享和通信

通过组合式函数,我们可以在不同组件之间共享状态:

// composables/useGlobalState.js
import { reactive } from 'vue'

const globalState = reactive({
  user: null,
  theme: 'light',
  language: 'zh-CN'
})

export function useGlobalState() {
  const setUser = (user) => {
    globalState.user = user
  }
  
  const setTheme = (theme) => {
    globalState.theme = theme
  }
  
  const setLanguage = (language) => {
    globalState.language = language
  }
  
  return {
    state: globalState,
    setUser,
    setTheme,
    setLanguage
  }
}

// 在组件中使用
import { useGlobalState } from '@/composables/useGlobalState'

export default {
  setup() {
    const { state, setUser, setTheme } = useGlobalState()
    
    return {
      user: state.user,
      theme: state.theme,
      setUser,
      setTheme
    }
  }
}

生命周期钩子管理

setup 函数详解

setup 是 Composition API 的入口函数,它在组件实例创建之前执行:

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

export default {
  setup(props, context) {
    // 组件属性和上下文访问
    console.log('Props:', props)
    console.log('Context:', context)
    
    const count = ref(0)
    const name = ref('')
    
    // 在组件挂载后执行
    onMounted(() => {
      console.log('Component mounted')
      // 初始化数据或订阅事件
      fetchInitialData()
    })
    
    // 在组件更新后执行
    onUpdated(() => {
      console.log('Component updated')
      // 处理更新后的逻辑
    })
    
    // 在组件销毁前执行
    onUnmounted(() => {
      console.log('Component unmounted')
      // 清理资源,取消订阅等
      cleanup()
    })
    
    const fetchInitialData = async () => {
      // 模拟数据获取
      await new Promise(resolve => setTimeout(resolve, 1000))
      name.value = 'Vue 3'
    }
    
    const cleanup = () => {
      // 清理定时器、事件监听器等
    }
    
    return {
      count,
      name
    }
  }
}

响应式生命周期钩子

除了传统的生命周期钩子,Vue 3 还提供了更灵活的响应式生命周期管理:

import { ref, watchEffect } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const name = ref('')
    
    // watchEffect 会自动追踪依赖
    watchEffect(() => {
      console.log(`Count: ${count.value}, Name: ${name.value}`)
      // 这个副作用函数会在 count 或 name 变化时重新执行
    })
    
    // 带有清理功能的 watchEffect
    const cleanup = watchEffect((onInvalidate) => {
      const timer = setTimeout(() => {
        console.log('Timer executed')
      }, 1000)
      
      // 注册清理函数
      onInvalidate(() => {
        clearTimeout(timer)
        console.log('Timer cleared')
      })
    })
    
    return {
      count,
      name
    }
  }
}

状态管理最佳实践

基于组合式函数的状态管理

我们可以创建一个完整的状态管理解决方案:

// stores/useUserStore.js
import { ref, computed } from 'vue'

export function useUserStore() {
  const users = ref([])
  const currentUser = ref(null)
  const loading = ref(false)
  
  // 获取用户列表
  const fetchUsers = async () => {
    try {
      loading.value = true
      const response = await fetch('/api/users')
      const data = await response.json()
      users.value = data
    } catch (error) {
      console.error('Failed to fetch users:', error)
    } finally {
      loading.value = false
    }
  }
  
  // 获取当前用户
  const fetchCurrentUser = async () => {
    try {
      loading.value = true
      const response = await fetch('/api/user')
      const data = await response.json()
      currentUser.value = data
    } catch (error) {
      console.error('Failed to fetch current user:', error)
    } finally {
      loading.value = false
    }
  }
  
  // 更新用户信息
  const updateUser = async (userData) => {
    try {
      loading.value = true
      const response = await fetch(`/api/user/${userData.id}`, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(userData)
      })
      const updatedUser = await response.json()
      
      // 更新本地状态
      if (currentUser.value?.id === updatedUser.id) {
        currentUser.value = updatedUser
      }
      
      const userIndex = users.value.findIndex(u => u.id === updatedUser.id)
      if (userIndex !== -1) {
        users.value[userIndex] = updatedUser
      }
    } catch (error) {
      console.error('Failed to update user:', error)
    } finally {
      loading.value = false
    }
  }
  
  // 计算属性
  const userCount = computed(() => users.value.length)
  const isLoggedIn = computed(() => !!currentUser.value)
  
  return {
    users,
    currentUser,
    loading,
    fetchUsers,
    fetchCurrentUser,
    updateUser,
    userCount,
    isLoggedIn
  }
}

// 在组件中使用
import { useUserStore } from '@/stores/useUserStore'

export default {
  setup() {
    const { 
      users, 
      currentUser, 
      loading, 
      fetchUsers, 
      fetchCurrentUser,
      userCount,
      isLoggedIn 
    } = useUserStore()
    
    // 组件挂载时获取数据
    onMounted(() => {
      fetchUsers()
      fetchCurrentUser()
    })
    
    return {
      users,
      currentUser,
      loading,
      userCount,
      isLoggedIn
    }
  }
}

多层级状态管理

对于更复杂的应用,我们可以实现多层状态管理:

// stores/index.js
import { reactive } from 'vue'

// 应用级别的全局状态
const appState = reactive({
  theme: 'light',
  language: 'zh-CN',
  notifications: []
})

// 用户相关的状态
const userState = reactive({
  profile: null,
  permissions: [],
  isAuthenticated: false
})

// 数据缓存状态
const cacheState = reactive({
  data: new Map(),
  lastUpdated: new Date()
})

// 状态管理器
export const useAppStore = () => {
  // 应用主题相关操作
  const setTheme = (theme) => {
    appState.theme = theme
  }
  
  const toggleTheme = () => {
    appState.theme = appState.theme === 'light' ? 'dark' : 'light'
  }
  
  // 用户状态相关操作
  const setUserProfile = (profile) => {
    userState.profile = profile
    userState.isAuthenticated = !!profile
  }
  
  const setPermissions = (permissions) => {
    userState.permissions = permissions
  }
  
  // 缓存相关操作
  const cacheData = (key, data) => {
    cacheState.data.set(key, data)
    cacheState.lastUpdated = new Date()
  }
  
  const getCachedData = (key) => {
    return cacheState.data.get(key)
  }
  
  const clearCache = () => {
    cacheState.data.clear()
    cacheState.lastUpdated = new Date()
  }
  
  return {
    // 应用状态
    theme: appState.theme,
    language: appState.language,
    notifications: appState.notifications,
    
    // 用户状态
    profile: userState.profile,
    permissions: userState.permissions,
    isAuthenticated: userState.isAuthenticated,
    
    // 缓存状态
    lastUpdated: cacheState.lastUpdated,
    
    // 方法
    setTheme,
    toggleTheme,
    setUserProfile,
    setPermissions,
    cacheData,
    getCachedData,
    clearCache
  }
}

高级模式和技巧

条件逻辑的处理

在复杂的业务场景中,我们经常需要根据条件动态地应用不同的逻辑:

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

export function useConditionalLogic(condition) {
  const isActive = ref(false)
  
  // 根据条件动态创建逻辑
  const conditionalLogic = computed(() => {
    if (condition.value === 'featureA') {
      return {
        name: 'Feature A',
        description: 'This is feature A logic',
        enabled: true
      }
    } else if (condition.value === 'featureB') {
      return {
        name: 'Feature B',
        description: 'This is feature B logic',
        enabled: false
      }
    } else {
      return {
        name: 'Default',
        description: 'Default logic',
        enabled: true
      }
    }
  })
  
  const toggleActive = () => {
    isActive.value = !isActive.value
  }
  
  return {
    isActive,
    conditionalLogic,
    toggleActive
  }
}

// 在组件中使用
import { useConditionalLogic } from '@/composables/useConditionalLogic'

export default {
  setup() {
    const condition = ref('featureA')
    const { isActive, conditionalLogic, toggleActive } = useConditionalLogic(condition)
    
    return {
      condition,
      isActive,
      conditionalLogic,
      toggleActive
    }
  }
}

异步操作和错误处理

在实际开发中,异步操作的处理是非常重要的:

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

export function useAsyncOperation() {
  const loading = ref(false)
  const error = ref(null)
  const data = ref(null)
  
  // 异步操作包装器
  const executeAsync = async (asyncFunction, ...args) => {
    try {
      loading.value = true
      error.value = null
      const result = await asyncFunction(...args)
      data.value = result
      return result
    } catch (err) {
      error.value = err
      throw err
    } finally {
      loading.value = false
    }
  }
  
  // 重试机制
  const retryAsync = async (asyncFunction, maxRetries = 3, ...args) => {
    let lastError
    for (let i = 0; i < maxRetries; i++) {
      try {
        return await executeAsync(asyncFunction, ...args)
      } catch (err) {
        lastError = err
        // 等待一段时间后重试
        await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)))
      }
    }
    throw lastError
  }
  
  const clear = () => {
    data.value = null
    error.value = null
    loading.value = false
  }
  
  return {
    loading,
    error,
    data,
    executeAsync,
    retryAsync,
    clear
  }
}

// 在组件中使用
import { useAsyncOperation } from '@/composables/useAsyncOperation'

export default {
  setup() {
    const { loading, error, data, executeAsync, retryAsync } = useAsyncOperation()
    
    const fetchData = async () => {
      try {
        await executeAsync(fetchDataFromApi, 'some-param')
      } catch (err) {
        console.error('Failed to fetch data:', err)
      }
    }
    
    const handleRetry = async () => {
      try {
        await retryAsync(fetchDataFromApi, 3, 'some-param')
      } catch (err) {
        console.error('Max retries exceeded:', err)
      }
    }
    
    return {
      loading,
      error,
      data,
      fetchData,
      handleRetry
    }
  }
}

性能优化技巧

在使用 Composition API 时,性能优化同样重要:

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

export function useMemoized(computation, dependencies) {
  const cache = ref(null)
  const lastDependencies = ref([])
  
  const result = computed(() => {
    // 检查依赖是否发生变化
    const depsChanged = dependencies.some((dep, index) => 
      dep !== lastDependencies.value[index]
    )
    
    if (depsChanged || !cache.value) {
      cache.value = computation()
      lastDependencies.value = [...dependencies]
    }
    
    return cache.value
  })
  
  return result
}

// 更高级的缓存机制
export function useCache() {
  const cache = new Map()
  
  const get = (key) => {
    return cache.get(key)
  }
  
  const set = (key, value) => {
    cache.set(key, value)
  }
  
  const has = (key) => {
    return cache.has(key)
  }
  
  const clear = () => {
    cache.clear()
  }
  
  return {
    get,
    set,
    has,
    clear
  }
}

实际应用场景

表单处理组件

让我们创建一个完整的表单处理组件示例:

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

export function useForm(initialData = {}) {
  const formData = ref({ ...initialData })
  const errors = ref({})
  const isSubmitting = ref(false)
  const isValidating = ref(false)
  
  // 验证规则
  const validationRules = ref({})
  
  // 设置验证规则
  const setRules = (rules) => {
    validationRules.value = rules
  }
  
  // 验证单个字段
  const validateField = (fieldName) => {
    const value = formData.value[fieldName]
    const rules = validationRules.value[fieldName]
    
    if (!rules) return true
    
    for (const rule of rules) {
      if (rule.required && !value) {
        errors.value[fieldName] = rule.message || `${fieldName} is required`
        return false
      }
      
      if (rule.minLength && value.length < rule.minLength) {
        errors.value[fieldName] = rule.message || `${fieldName} must be at least ${rule.minLength} characters`
        return false
      }
      
      // 更多验证规则...
    }
    
    delete errors.value[fieldName]
    return true
  }
  
  // 验证所有字段
  const validateAll = () => {
    const fields = Object.keys(validationRules.value)
    let isValid = true
    
    fields.forEach(field => {
      if (!validateField(field)) {
        isValid = false
      }
    })
    
    return isValid
  }
  
  // 更新表单数据
  const updateField = (fieldName, value) => {
    formData.value[fieldName] = value
    delete errors.value[fieldName]
  }
  
  // 提交表单
  const submit = async (submitFunction) => {
    if (!validateAll()) return false
    
    try {
      isSubmitting.value = true
      const result = await submitFunction(formData.value)
      return result
    } catch (error) {
      console.error('Form submission error:', error)
      return false
    } finally {
      isSubmitting.value = false
    }
  }
  
  // 重置表单
  const reset = () => {
    formData.value = { ...initialData }
    errors.value = {}
  }
  
  // 计算属性
  const isValid = computed(() => {
    return Object.keys(errors.value).length === 0 && !isSubmitting.value
  })
  
  return {
    formData,
    errors,
    isSubmitting,
    isValidating,
    setRules,
    validateField,
    validateAll,
    updateField,
    submit,
    reset,
    isValid
  }
}

// 使用示例组件
import { useForm } from '@/composables/useForm'

export default {
  setup() {
    const { 
      formData, 
      errors, 
      isSubmitting, 
      setRules, 
      updateField, 
      submit 
    } = useForm({
      name: '',
      email: '',
      password: ''
    })
    
    // 设置验证规则
    setRules({
      name: [
        { required: true, message: 'Name is required' },
        { minLength: 2, message: 'Name must be at least 2 characters' }
      ],
      email: [
        { required: true, message: 'Email is required' },
        { pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: 'Invalid email format' }
      ],
      password: [
        { required: true, message: 'Password is required' },
        { minLength: 6, message: 'Password must be at least 6 characters' }
      ]
    })
    
    const handleSubmit = async (e) => {
      e.preventDefault()
      const result = await submit(async (data) => {
        // 模拟 API 调用
        return new Promise(resolve => {
          setTimeout(() => resolve({ success: true, data }), 1000)
        })
      })
      
      if (result?.success) {
        console.log('Form submitted successfully')
      }
    }
    
    return {
      formData,
      errors,
      isSubmitting,
      updateField,
      handleSubmit
    }
  }
}

数据表格组件

另一个实用的场景是创建可复用的数据表格组件:

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

export function useDataTable(data = [], options = {}) {
  const currentPage = ref(1)
  const pageSize = ref(options.pageSize || 10)
  const sortBy = ref(options.sortBy || null)
  const sortDirection = ref(options.sortDirection || 'asc')
  const searchQuery = ref('')
  
  // 过滤数据
  const filteredData = computed(() => {
    if (!searchQuery.value) return data
    
    return data.filter(item => {
      return Object.values(item).some(value => 
        value.toString().toLowerCase().includes(searchQuery.value.toLowerCase())
      )
    })
  })
  
  // 排序数据
  const sortedData = computed(() => {
    if (!sortBy.value) return filteredData.value
    
    return [...filteredData.value].sort((a, b) => {
      const aValue = a[sortBy.value]
      const bValue = b[sortBy.value]
      
      if (aValue < bValue) return sortDirection.value === 'asc' ? -1 : 1
      if (aValue > bValue) return sortDirection.value === 'asc' ? 1 : -1
      return 0
    })
  })
  
  // 分页数据
  const paginatedData = computed(() => {
    const start = (currentPage.value - 1) * pageSize.value
    const end = start + pageSize.value
    return sortedData.value.slice(start, end)
  })
  
  // 总页数
  const totalPages = computed(() => {
    return Math.ceil(sortedData.value.length / pageSize.value)
  })
  
  // 总记录数
  const totalRecords = computed(() => {
    return sortedData.value.length
  })
  
  // 排序方法
  const sort = (field) => {
    if (sortBy.value === field) {
      sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc'
    } else {
      sortBy.value = field
      sortDirection.value = 'asc'
    }
  }
  
  // 分页方法
  const goToPage = (page) => {
    if (page >= 1 && page <= totalPages.value) {
      currentPage.value = page
    }
  }
  
  const nextPage = () => {
    if (currentPage.value < totalPages.value) {
      currentPage.value++
    }
  }
  
  const prevPage = () => {
    if (currentPage.value > 1) {
      currentPage.value--
    }
  }
  
  // 搜索方法
  const search = (query) => {
    searchQuery.value = query
    currentPage.value = 1
  }
  
  // 重置分页
  const resetPagination = () => {
    currentPage.value = 1
    searchQuery.value = ''
  }
  
  return {
    currentPage,
    pageSize,
    sortBy,
    sortDirection,
    searchQuery,
    filteredData,
    sortedData,
    paginatedData,
    totalPages,
    totalRecords,
    sort,
    goToPage,
    nextPage,
    prevPage,
    search,
    resetPagination
  }
}

// 使用示例
import { useDataTable } from '@/composables/useDataTable'

export default {
  setup() {
    const tableData = ref([
      { id: 1, name: 'John', email: 'john@example.com', age: 25 },
      { id: 2, name: 'Jane', email: 'jane@example.com', age: 30 },
      { id: 3, name: 'Bob', email: 'bob@example.com', age: 35 }
    ])
    
    const {
      paginatedData,
      totalPages,
      currentPage,
      goToPage,
      sort,
      search
    } = useDataTable(tableData.value, {
      pageSize: 5,
      sortBy: 'name',
      sortDirection: 'asc'
    })
    
    return {
      tableData,
      paginatedData,
      totalPages,
      currentPage,
      goToPage,
      sort,
      search
    }
  }
}

总结

Vue 3 Composition API 为我们提供了一种更加灵活和强大的组件开发方式。通过合理使用 refreactivecomputedwatch 等响应式API,我们可以更好地组织代码逻辑,提高组件的可复用性和可维护性。

在实际开发中,组合式函数是实现逻辑复用的核心工具,它让我们能够将通用的业务逻辑封装成独立的可复用单元。同时,通过合理的状态管理实践,我们可以在应用的不同层级之间有效地共享和管理状态。

性能优化同样重要,我们需要关注响应式数据的使用方式、计算属性的缓存机制以及异步操作的处理策略。通过这些最佳实践,我们可以构建出

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000