Vue 3 Composition API最佳实践:从基础语法到复杂组件设计

StaleMaster
StaleMaster 2026-02-10T07:02:09+08:00
0 0 0

引言

Vue 3的发布带来了革命性的变化,其中最引人注目的便是Composition API的引入。相比Vue 2中的Options API,Composition API为开发者提供了更加灵活、可组合的组件开发方式。本文将深入探讨Composition API的核心概念、使用技巧,并结合实际项目经验分享组件化开发、状态管理、性能优化等最佳实践方法。

Composition API核心概念

什么是Composition API

Composition API是Vue 3中引入的一种新的组件逻辑组织方式。它允许开发者以函数的形式组织和重用组件逻辑,打破了传统Options API中基于选项的组织方式。通过Composition API,我们可以将相关的逻辑代码组合在一起,而不是按照属性、方法等分类。

核心响应式API

Composition API的核心是响应式系统,主要包括以下关键API:

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

// Ref:创建响应式引用
const count = ref(0)
console.log(count.value) // 0
count.value = 1
console.log(count.value) // 1

// Reactive:创建响应式对象
const state = reactive({
  name: 'Vue',
  version: 3
})

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

// Watch:监听响应式数据变化
watch(count, (newVal, oldVal) => {
  console.log(`count changed from ${oldVal} to ${newVal}`)
})

setup函数

setup是Composition API的入口函数,它在组件实例创建之前执行。在这个函数中,我们可以访问所有响应式API:

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

export default {
  setup() {
    // 响应式数据定义
    const count = ref(0)
    const state = reactive({
      name: 'Vue',
      version: 3
    })
    
    // 计算属性
    const doubleCount = computed(() => count.value * 2)
    
    // 方法定义
    const increment = () => {
      count.value++
    }
    
    // 返回给模板使用
    return {
      count,
      state,
      doubleCount,
      increment
    }
  }
}

基础语法实践

响应式数据管理

在Composition API中,响应式数据的管理更加直观和灵活。让我们看看如何有效地管理不同类型的数据:

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

export default {
  setup() {
    // 基本类型响应式数据
    const count = ref(0)
    const message = ref('Hello Vue')
    
    // 对象类型响应式数据
    const user = reactive({
      name: 'John',
      age: 25,
      email: 'john@example.com'
    })
    
    // 数组类型响应式数据
    const items = reactive([
      { id: 1, name: 'Item 1' },
      { id: 2, name: 'Item 2' }
    ])
    
    // 计算属性
    const fullName = computed(() => {
      return `${user.name} (${user.age})`
    })
    
    // 方法
    const addItem = (name) => {
      items.push({ id: Date.now(), name })
    }
    
    const updateUserAge = (age) => {
      user.age = age
    }
    
    // 使用toRefs解构响应式数据
    const { name, age } = toRefs(user)
    
    return {
      count,
      message,
      user,
      items,
      fullName,
      addItem,
      updateUserAge,
      name,
      age
    }
  }
}

生命周期钩子

Composition API提供了对应的生命周期钩子函数,让我们能够更好地控制组件的生命周期:

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

export default {
  setup() {
    const data = ref(null)
    
    // 组件挂载前
    onBeforeMount(() => {
      console.log('组件即将挂载')
    })
    
    // 组件挂载后
    onMounted(() => {
      console.log('组件已挂载')
      // 可以在这里进行DOM操作
      data.value = 'Component mounted'
    })
    
    // 组件更新前
    onBeforeUpdate(() => {
      console.log('组件即将更新')
    })
    
    // 组件更新后
    onUpdated(() => {
      console.log('组件已更新')
    })
    
    // 组件卸载前
    onBeforeUnmount(() => {
      console.log('组件即将卸载')
    })
    
    // 组件卸载后
    onUnmounted(() => {
      console.log('组件已卸载')
    })
    
    return {
      data
    }
  }
}

高级实践技巧

组合式函数(Composables)

组合式函数是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 double = computed(() => count.value * 2)
  
  return {
    count,
    increment,
    decrement,
    reset,
    double
  }
}

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

export function useFetch(url) {
  const data = ref(null)
  const loading = ref(false)
  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(`HTTP error! status: ${response.status}`)
      }
      data.value = await response.json()
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }
  
  // 自动调用fetchData
  watch(url, fetchData, { immediate: true })
  
  return {
    data,
    loading,
    error,
    refetch: fetchData
  }
}

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

export default {
  setup() {
    const { count, increment, decrement, reset, double } = useCounter(10)
    const { data, loading, error, refetch } = useFetch('/api/users')
    
    return {
      count,
      increment,
      decrement,
      reset,
      double,
      data,
      loading,
      error,
      refetch
    }
  }
}

响应式数据的深层操作

对于复杂的嵌套响应式对象,我们需要特别注意如何正确地进行数据更新:

import { ref, reactive, set, del } from 'vue'

export default {
  setup() {
    const state = reactive({
      user: {
        profile: {
          name: 'John',
          age: 25
        }
      },
      items: []
    })
    
    // 正确的深层对象更新方式
    const updateUserProfile = (newProfile) => {
      // 使用set函数确保响应式
      set(state.user, 'profile', newProfile)
    }
    
    // 或者直接替换整个对象
    const updateUserName = (name) => {
      state.user.profile.name = name
    }
    
    // 添加数组元素
    const addItem = (item) => {
      state.items.push(item)
    }
    
    // 删除数组元素
    const removeItem = (index) => {
      state.items.splice(index, 1)
    }
    
    return {
      state,
      updateUserProfile,
      updateUserName,
      addItem,
      removeItem
    }
  }
}

组件化开发最佳实践

复杂组件的设计模式

在实际项目中,我们经常需要处理复杂的组件逻辑。使用Composition API可以更好地组织这些复杂逻辑:

// components/UserProfile.vue
import { ref, reactive, computed } from 'vue'
import { useFetch } from '@/composables/useFetch'

export default {
  props: {
    userId: {
      type: Number,
      required: true
    }
  },
  
  setup(props) {
    // 响应式状态
    const isLoading = ref(false)
    const isEditing = ref(false)
    const editForm = reactive({
      name: '',
      email: '',
      phone: ''
    })
    
    // 使用composable获取用户数据
    const { data: user, loading, error } = useFetch(`/api/users/${props.userId}`)
    
    // 计算属性
    const hasUser = computed(() => !!user.value)
    const displayName = computed(() => {
      return user.value ? `${user.value.firstName} ${user.value.lastName}` : 'Unknown User'
    })
    
    // 方法
    const loadUser = async () => {
      if (props.userId) {
        isLoading.value = true
        try {
          // 这里可以重新获取数据
        } finally {
          isLoading.value = false
        }
      }
    }
    
    const startEdit = () => {
      if (user.value) {
        Object.assign(editForm, user.value)
        isEditing.value = true
      }
    }
    
    const cancelEdit = () => {
      isEditing.value = false
    }
    
    const saveUser = async () => {
      try {
        // 发送更新请求
        await fetch(`/api/users/${props.userId}`, {
          method: 'PUT',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(editForm)
        })
        
        isEditing.value = false
        // 重新加载用户数据
        await loadUser()
      } catch (err) {
        console.error('Failed to save user:', err)
      }
    }
    
    return {
      isLoading,
      isEditing,
      editForm,
      user,
      loading,
      error,
      hasUser,
      displayName,
      loadUser,
      startEdit,
      cancelEdit,
      saveUser
    }
  }
}

组件通信的最佳实践

在Vue 3中,组件间通信可以通过多种方式实现,使用Composition API可以更加优雅地处理这些场景:

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

// 全局状态管理
const globalState = reactive({
  theme: 'light',
  language: 'zh-CN',
  user: null
})

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

// 在组件中使用全局状态
import { useGlobalState } from '@/composables/useGlobalState'

export default {
  setup() {
    const { state, setTheme, setLanguage } = useGlobalState()
    
    // 监听全局状态变化
    watch(() => state.theme, (newTheme) => {
      document.body.className = newTheme
    })
    
    return {
      theme: computed(() => state.theme),
      language: computed(() => state.language),
      user: computed(() => state.user),
      setTheme,
      setLanguage
    }
  }
}

状态管理深度解析

组合式函数状态管理

在复杂应用中,我们可以使用组合式函数来实现轻量级的状态管理:

// composables/useStore.js
import { ref, reactive } from 'vue'

export function useStore() {
  // 状态存储
  const state = reactive({
    todos: [],
    filters: {
      status: 'all',
      search: ''
    },
    ui: {
      loading: false,
      error: null
    }
  })
  
  // 计算属性
  const filteredTodos = computed(() => {
    let result = state.todos
    
    if (state.filters.status !== 'all') {
      result = result.filter(todo => todo.status === state.filters.status)
    }
    
    if (state.filters.search) {
      const searchLower = state.filters.search.toLowerCase()
      result = result.filter(todo => 
        todo.title.toLowerCase().includes(searchLower) ||
        todo.description.toLowerCase().includes(searchLower)
      )
    }
    
    return result
  })
  
  // 方法
  const addTodo = (todo) => {
    state.todos.push({
      id: Date.now(),
      ...todo,
      createdAt: new Date()
    })
  }
  
  const updateTodo = (id, updates) => {
    const index = state.todos.findIndex(todo => todo.id === id)
    if (index !== -1) {
      Object.assign(state.todos[index], updates)
    }
  }
  
  const deleteTodo = (id) => {
    const index = state.todos.findIndex(todo => todo.id === id)
    if (index !== -1) {
      state.todos.splice(index, 1)
    }
  }
  
  const setFilter = (filterKey, value) => {
    state.filters[filterKey] = value
  }
  
  const setLoading = (loading) => {
    state.ui.loading = loading
  }
  
  const setError = (error) => {
    state.ui.error = error
  }
  
  return {
    state,
    filteredTodos,
    addTodo,
    updateTodo,
    deleteTodo,
    setFilter,
    setLoading,
    setError
  }
}

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

export default {
  setup() {
    const { 
      state, 
      filteredTodos, 
      addTodo, 
      updateTodo, 
      deleteTodo, 
      setFilter,
      setLoading,
      setError
    } = useStore()
    
    return {
      todos: filteredTodos,
      addTodo,
      updateTodo,
      deleteTodo,
      setFilter,
      setLoading,
      setError
    }
  }
}

状态持久化方案

在实际项目中,我们经常需要将状态持久化到localStorage或sessionStorage中:

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

export function usePersistentState(key, defaultValue) {
  // 从localStorage初始化状态
  const state = ref(
    localStorage.getItem(key) 
      ? JSON.parse(localStorage.getItem(key))
      : defaultValue
  )
  
  // 监听状态变化并保存到localStorage
  watch(state, (newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue))
  }, { deep: true })
  
  // 清除状态
  const clear = () => {
    state.value = defaultValue
    localStorage.removeItem(key)
  }
  
  return {
    state,
    clear
  }
}

// 使用示例
export default {
  setup() {
    const { state: preferences, clear: clearPreferences } = usePersistentState('user-preferences', {
      theme: 'light',
      language: 'zh-CN',
      notifications: true
    })
    
    return {
      preferences,
      clearPreferences
    }
  }
}

性能优化策略

计算属性和缓存机制

合理使用计算属性可以显著提升应用性能:

import { ref, computed } from 'vue'

export default {
  setup() {
    const items = ref([])
    const filterText = ref('')
    
    // 复杂的计算属性,应该使用computed进行缓存
    const filteredItems = computed(() => {
      if (!filterText.value) return items.value
      
      return items.value.filter(item => 
        item.name.toLowerCase().includes(filterText.value.toLowerCase())
      )
    })
    
    // 多个依赖项的复杂计算
    const expensiveCalculation = computed(() => {
      // 这个计算可能很耗时,但会被缓存
      return items.value.reduce((acc, item) => {
        acc += item.price * item.quantity
        return acc
      }, 0)
    })
    
    // 带有getter和setter的计算属性
    const fullName = computed({
      get: () => `${user.firstName} ${user.lastName}`,
      set: (value) => {
        const names = value.split(' ')
        user.firstName = names[0]
        user.lastName = names[1] || ''
      }
    })
    
    return {
      items,
      filterText,
      filteredItems,
      expensiveCalculation,
      fullName
    }
  }
}

监听器优化

监听器的使用需要谨慎,避免不必要的性能开销:

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

export default {
  setup() {
    const count = ref(0)
    const name = ref('')
    const email = ref('')
    
    // 基础watch用法
    watch(count, (newVal, oldVal) => {
      console.log(`count changed from ${oldVal} to ${newVal}`)
    })
    
    // 监听多个值
    watch([name, email], ([newName, newEmail], [oldName, oldEmail]) => {
      console.log('Name or email changed')
    })
    
    // 深度监听
    const user = ref({
      profile: {
        name: 'John',
        settings: {
          theme: 'light'
        }
      }
    })
    
    watch(user, (newUser) => {
      console.log('User changed:', newUser)
    }, { deep: true })
    
    // 使用watchEffect,自动追踪依赖
    watchEffect(() => {
      // 这里会自动追踪所有响应式数据的使用
      console.log(`Name: ${name.value}, Email: ${email.value}`)
    })
    
    // 防抖监听
    const debouncedWatch = (source, callback, delay = 300) => {
      let timeoutId
      return watch(source, (...args) => {
        clearTimeout(timeoutId)
        timeoutId = setTimeout(() => callback(...args), delay)
      })
    }
    
    // 使用防抖监听搜索输入
    const searchQuery = ref('')
    debouncedWatch(searchQuery, (newQuery) => {
      if (newQuery.length > 2) {
        // 执行搜索逻辑
        console.log('Searching for:', newQuery)
      }
    }, 500)
    
    return {
      count,
      name,
      email,
      user,
      searchQuery
    }
  }
}

组件懒加载和性能监控

// composables/usePerformanceMonitor.js
import { ref, onMounted, onUnmounted } from 'vue'

export function usePerformanceMonitor() {
  const performanceData = ref({
    loadTime: 0,
    memoryUsage: 0,
    renderCount: 0
  })
  
  let startTime = 0
  
  const startMonitoring = () => {
    startTime = performance.now()
    
    // 监听组件渲染次数
    const observer = new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        if (entry.entryType === 'paint') {
          performanceData.value.renderCount++
        }
      })
    })
    
    observer.observe({ entryTypes: ['paint'] })
  }
  
  const stopMonitoring = () => {
    const endTime = performance.now()
    performanceData.value.loadTime = endTime - startTime
  }
  
  onMounted(() => {
    startMonitoring()
  })
  
  onUnmounted(() => {
    stopMonitoring()
  })
  
  return {
    performanceData
  }
}

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

export default {
  setup() {
    const { performanceData } = usePerformanceMonitor()
    
    return {
      performanceData
    }
  }
}

实际项目应用案例

电商商品列表组件

让我们通过一个实际的电商商品列表组件来展示Composition API的强大功能:

<template>
  <div class="product-list">
    <!-- 搜索和筛选 -->
    <div class="controls">
      <input 
        v-model="searchQuery" 
        placeholder="搜索商品..."
        class="search-input"
      />
      <select v-model="selectedCategory" class="category-select">
        <option value="">所有分类</option>
        <option v-for="category in categories" :key="category.id" :value="category.id">
          {{ category.name }}
        </option>
      </select>
    </div>
    
    <!-- 加载状态 -->
    <div v-if="loading" class="loading">加载中...</div>
    
    <!-- 错误处理 -->
    <div v-else-if="error" class="error">
      {{ error }}
    </div>
    
    <!-- 商品列表 -->
    <div v-else class="products-grid">
      <ProductCard 
        v-for="product in filteredProducts" 
        :key="product.id"
        :product="product"
        @favorite="toggleFavorite"
      />
    </div>
    
    <!-- 分页 -->
    <Pagination 
      v-if="totalPages > 1"
      :current-page="currentPage"
      :total-pages="totalPages"
      @page-change="changePage"
    />
  </div>
</template>

<script>
import { ref, reactive, computed, watch, onMounted } from 'vue'
import ProductCard from './ProductCard.vue'
import Pagination from './Pagination.vue'

export default {
  name: 'ProductList',
  components: {
    ProductCard,
    Pagination
  },
  
  setup() {
    // 响应式数据
    const products = ref([])
    const loading = ref(false)
    const error = ref(null)
    
    const filters = reactive({
      searchQuery: '',
      category: '',
      sortBy: 'name',
      sortOrder: 'asc'
    })
    
    const pagination = reactive({
      currentPage: 1,
      pageSize: 20,
      total: 0
    })
    
    // 计算属性
    const filteredProducts = computed(() => {
      let result = [...products.value]
      
      // 搜索过滤
      if (filters.searchQuery) {
        const query = filters.searchQuery.toLowerCase()
        result = result.filter(product => 
          product.name.toLowerCase().includes(query) ||
          product.description.toLowerCase().includes(query)
        )
      }
      
      // 分类过滤
      if (filters.category) {
        result = result.filter(product => product.categoryId === filters.category)
      }
      
      // 排序
      result.sort((a, b) => {
        let aValue = a[filters.sortBy]
        let bValue = b[filters.sortBy]
        
        if (typeof aValue === 'string') {
          aValue = aValue.toLowerCase()
          bValue = bValue.toLowerCase()
        }
        
        if (filters.sortOrder === 'asc') {
          return aValue > bValue ? 1 : -1
        } else {
          return aValue < bValue ? 1 : -1
        }
      })
      
      return result
    })
    
    const totalPages = computed(() => {
      return Math.ceil(filteredProducts.value.length / pagination.pageSize)
    })
    
    const paginatedProducts = computed(() => {
      const start = (pagination.currentPage - 1) * pagination.pageSize
      const end = start + pagination.pageSize
      return filteredProducts.value.slice(start, end)
    })
    
    // 方法
    const fetchProducts = async () => {
      loading.value = true
      error.value = null
      
      try {
        const response = await fetch('/api/products', {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json'
          }
        })
        
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`)
        }
        
        const data = await response.json()
        products.value = data.products
        pagination.total = data.total
      } catch (err) {
        error.value = err.message
        console.error('Failed to fetch products:', err)
      } finally {
        loading.value = false
      }
    }
    
    const toggleFavorite = (productId) => {
      const product = products.value.find(p => p.id === productId)
      if (product) {
        product.isFavorite = !product.isFavorite
      }
    }
    
    const changePage = (page) => {
      pagination.currentPage = page
      // 可以在这里添加滚动到顶部的逻辑
      window.scrollTo({ top: 0, behavior: 'smooth' })
    }
    
    const setSearchQuery = (query) => {
      filters.searchQuery = query
      pagination.currentPage = 1
    }
    
    const setCategory = (category) => {
      filters.category = category
      pagination.currentPage = 1
    }
    
    // 监听筛选条件变化
    watch([() => filters.searchQuery, () => filters.category], () => {
      pagination.currentPage = 1
    })
    
    // 组件挂载时获取数据
    onMounted(() => {
      fetchProducts()
    })
    
    return {
      products,
      loading,
      error,
      searchQuery: computed(() => filters.searchQuery),
      selectedCategory: computed(() => filters.category),
      filteredProducts: paginatedProducts,
      totalPages,
      currentPage: computed(() => pagination.currentPage),
      
      // 方法
      toggleFavorite,
      changePage,
      setSearchQuery,
      setCategory
    }
  }
}
</script>

<style scoped>
.product-list {
  padding: 20px;
}

.controls {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}

.search-input, .category-select {
  padding: 8px 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 14px;
}

.loading, .error {
  text-align: center;
  padding: 20px;
}

.products-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 20px;
}
</style>

用户管理系统

另一个复杂的实际应用案例是用户管理系统:

<template>
  <div class="user-management">
    <!-- 工具栏 -->
    <div class="toolbar">
      <button @click="showCreateForm = true" class="btn btn-primary">添加用户</button>
      <input 
        v-model="searchQuery" 
        placeholder="搜索用户..."
        class="search-input"
      />
    </div>
    
    <!-- 用户列表 -->
    <div class="user-list">
      <UserRow 
        v-for="user in paginatedUsers" 
        :key="user.id"
        :user="user"
        @edit="handleEdit"
        @delete="handleDelete"
      />
    </div>
    
    <!-- 分页 -->
    <Pagination 
      :current-page="currentPage"
      :total-pages="totalPages"
      @page-change="changePage"
    />
    
    <!-- 用户表单模态框 -->
    <UserForm 
      v-if="showCreateForm || editingUser"
      :user="editingUser || {}"
      :is-editing="!!editingUser"
      @save="handleSave"
      @cancel="handleCancel"
    />
  </div>
</template>

<script>
import { ref, reactive, computed, watch, onMounted } from 'vue'
import UserRow from './UserRow.vue'
import UserForm from './UserForm.vue'
import Pagination from './Pagination.vue'

export default
相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000