Vue 3 Composition API实战:构建响应式数据流与组件通信模式

OldEdward
OldEdward 2026-02-06T07:04:01+08:00
0 0 0

引言

Vue 3 的发布带来了全新的 Composition API,这一创新性的 API 设计理念彻底改变了我们编写 Vue 组件的方式。相比于传统的 Options API,Composition API 提供了更加灵活、可复用的代码组织方式,特别是在处理复杂业务逻辑时展现出显著优势。

本文将深入探讨 Vue 3 Composition API 的核心概念,并通过实际项目案例演示如何构建响应式数据流、实现组件间通信以及进行逻辑复用。我们将从基础概念入手,逐步深入到高级特性,帮助开发者掌握这一现代前端开发工具的核心技能。

Vue 3 Composition API 核心概念

什么是 Composition API

Composition API 是 Vue 3 中引入的一种新的组件逻辑组织方式。它允许我们通过组合函数来组织和复用组件逻辑,而不是按照传统的选项(options)方式将代码分散在 datamethodscomputed 等不同选项中。

核心响应式函数

Composition API 的核心是几个基础的响应式函数:

  • ref():创建响应式的数据引用
  • reactive():创建响应式的对象
  • computed():创建计算属性
  • watch():监听数据变化
  • watchEffect():自动追踪依赖的变化
import { ref, reactive, computed, watch } from 'vue'

// 创建响应式数据
const count = ref(0)
const person = reactive({
  name: 'John',
  age: 30
})

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

// 监听器
watch(count, (newVal, oldVal) => {
  console.log(`count changed from ${oldVal} to ${newVal}`)
})

响应式数据管理实战

基础响应式数据操作

在实际开发中,我们需要处理各种复杂的响应式数据场景。让我们通过一个用户管理系统来演示基础操作:

// userStore.js
import { ref, reactive, computed } from 'vue'

export function useUserStore() {
  // 用户列表
  const users = ref([])
  
  // 当前选中的用户
  const selectedUser = ref(null)
  
  // 搜索关键词
  const searchKeyword = ref('')
  
  // 添加用户
  const addUser = (user) => {
    users.value.push({ ...user, id: Date.now() })
  }
  
  // 删除用户
  const deleteUser = (id) => {
    users.value = users.value.filter(user => user.id !== id)
  }
  
  // 更新用户
  const updateUser = (id, updates) => {
    const index = users.value.findIndex(user => user.id === id)
    if (index !== -1) {
      users.value[index] = { ...users.value[index], ...updates }
    }
  }
  
  // 搜索用户
  const filteredUsers = computed(() => {
    if (!searchKeyword.value) return users.value
    
    return users.value.filter(user => 
      user.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
      user.email.toLowerCase().includes(searchKeyword.value.toLowerCase())
    )
  })
  
  return {
    users,
    selectedUser,
    searchKeyword,
    addUser,
    deleteUser,
    updateUser,
    filteredUsers
  }
}

复杂数据结构管理

在实际项目中,我们经常需要处理嵌套的复杂数据结构。以下是一个购物车系统的实现:

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

export function useCartStore() {
  // 购物车商品列表
  const items = ref([])
  
  // 添加商品到购物车
  const addToCart = (product) => {
    const existingItem = items.value.find(item => item.id === product.id)
    
    if (existingItem) {
      existingItem.quantity += 1
    } else {
      items.value.push({
        ...product,
        quantity: 1
      })
    }
  }
  
  // 移除商品
  const removeFromCart = (productId) => {
    items.value = items.value.filter(item => item.id !== productId)
  }
  
  // 更新商品数量
  const updateQuantity = (productId, quantity) => {
    const item = items.value.find(item => item.id === productId)
    if (item) {
      item.quantity = Math.max(0, quantity)
      if (item.quantity === 0) {
        removeFromCart(productId)
      }
    }
  }
  
  // 计算总价
  const totalPrice = computed(() => {
    return items.value.reduce((total, item) => {
      return total + (item.price * item.quantity)
    }, 0)
  })
  
  // 计算商品总数
  const totalItems = computed(() => {
    return items.value.reduce((total, item) => total + item.quantity, 0)
  })
  
  // 清空购物车
  const clearCart = () => {
    items.value = []
  }
  
  return {
    items,
    addToCart,
    removeFromCart,
    updateQuantity,
    totalPrice,
    totalItems,
    clearCart
  }
}

组件间通信模式

父子组件通信

在 Vue 3 中,父子组件通信可以通过 props 和 emit 实现,结合 Composition API 可以更好地组织代码:

<!-- Parent.vue -->
<template>
  <div class="parent">
    <h2>父组件</h2>
    <Child 
      :user-data="userData" 
      @update-user="handleUpdateUser"
      @delete-user="handleDeleteUser"
    />
    <button @click="addNewUser">添加用户</button>
  </div>
</template>

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

const userData = ref({
  name: '张三',
  age: 25,
  email: 'zhangsan@example.com'
})

const handleUpdateUser = (updatedData) => {
  userData.value = { ...userData.value, ...updatedData }
}

const handleDeleteUser = () => {
  userData.value = null
}

const addNewUser = () => {
  userData.value = {
    name: '新用户',
    age: 30,
    email: 'newuser@example.com'
  }
}
</script>
<!-- Child.vue -->
<template>
  <div class="child">
    <h3>子组件</h3>
    <div v-if="userData">
      <p>姓名: {{ userData.name }}</p>
      <p>年龄: {{ userData.age }}</p>
      <p>邮箱: {{ userData.email }}</p>
      <button @click="updateUser">更新用户</button>
      <button @click="deleteUser">删除用户</button>
    </div>
    <div v-else>
      <p>暂无用户数据</p>
    </div>
  </div>
</template>

<script setup>
import { defineProps, defineEmits } from 'vue'

const props = defineProps({
  userData: {
    type: Object,
    default: null
  }
})

const emit = defineEmits(['updateUser', 'deleteUser'])

const updateUser = () => {
  emit('updateUser', {
    name: props.userData.name + ' (更新)',
    age: props.userData.age + 1
  })
}

const deleteUser = () => {
  emit('deleteUser')
}
</script>

兄弟组件通信

在兄弟组件间通信时,可以使用事件总线或状态管理方案。以下是一个基于 provide/inject 的实现:

<!-- App.vue -->
<template>
  <div class="app">
    <ComponentA />
    <ComponentB />
  </div>
</template>

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

// 创建共享状态
const sharedMessage = ref('Hello from App')
const sharedData = ref({ count: 0 })

// 提供给子组件
provide('sharedMessage', sharedMessage)
provide('sharedData', sharedData)

// 更新共享数据的方法
const updateSharedData = (newData) => {
  sharedData.value = { ...sharedData.value, ...newData }
}

provide('updateSharedData', updateSharedData)
</script>
<!-- ComponentA.vue -->
<template>
  <div class="component-a">
    <h3>组件 A</h3>
    <p>共享消息: {{ sharedMessage }}</p>
    <p>共享数据: {{ sharedData.count }}</p>
    <button @click="incrementCount">增加计数</button>
  </div>
</template>

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

const sharedMessage = inject('sharedMessage')
const sharedData = inject('sharedData')
const updateSharedData = inject('updateSharedData')

const incrementCount = () => {
  updateSharedData({ count: sharedData.value.count + 1 })
}
</script>
<!-- ComponentB.vue -->
<template>
  <div class="component-b">
    <h3>组件 B</h3>
    <p>共享消息: {{ sharedMessage }}</p>
    <p>共享数据: {{ sharedData.count }}</p>
    <button @click="decrementCount">减少计数</button>
  </div>
</template>

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

const sharedMessage = inject('sharedMessage')
const sharedData = inject('sharedData')
const updateSharedData = inject('updateSharedData')

const decrementCount = () => {
  updateSharedData({ count: sharedData.value.count - 1 })
}
</script>

逻辑复用与自定义组合式函数

创建可复用的组合式函数

Vue 3 的 Composition API 最大的优势之一就是能够轻松地创建可复用的逻辑。让我们创建一个数据获取和加载状态管理的组合式函数:

// 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.message || '请求失败'
      console.error('API Error:', err)
    } finally {
      loading.value = false
    }
  }
  
  const refresh = () => {
    if (data.value !== null) {
      fetchData()
    }
  }
  
  const clear = () => {
    data.value = initialData
    error.value = null
  }
  
  // 计算属性:检查是否已加载数据
  const hasData = computed(() => data.value !== null)
  
  // 计算属性:检查是否有错误
  const hasError = computed(() => error.value !== null)
  
  return {
    data,
    loading,
    error,
    fetchData,
    refresh,
    clear,
    hasData,
    hasError
  }
}

实际应用示例

让我们使用这个组合式函数来创建一个用户列表组件:

<!-- UserList.vue -->
<template>
  <div class="user-list">
    <div class="controls">
      <input 
        v-model="searchQuery" 
        placeholder="搜索用户..."
        @keyup.enter="searchUsers"
      />
      <button @click="searchUsers">搜索</button>
      <button @click="refreshData" :disabled="loading">刷新</button>
    </div>
    
    <div v-if="loading" class="loading">
      加载中...
    </div>
    
    <div v-else-if="hasError" class="error">
      错误: {{ error }}
    </div>
    
    <div v-else-if="!hasData" class="empty">
      暂无用户数据
    </div>
    
    <ul v-else class="users">
      <li v-for="user in data" :key="user.id" class="user-item">
        <h4>{{ user.name }}</h4>
        <p>{{ user.email }}</p>
        <p>年龄: {{ user.age }}</p>
      </li>
    </ul>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'
import { useApi } from './composables/useApi'

// 模拟 API 调用
const fetchUsers = async (query = '') => {
  // 模拟网络延迟
  await new Promise(resolve => setTimeout(resolve, 1000))
  
  // 模拟返回数据
  const mockUsers = [
    { id: 1, name: '张三', email: 'zhangsan@example.com', age: 25 },
    { id: 2, name: '李四', email: 'lisi@example.com', age: 30 },
    { id: 3, name: '王五', email: 'wangwu@example.com', age: 35 }
  ]
  
  if (query) {
    return mockUsers.filter(user => 
      user.name.toLowerCase().includes(query.toLowerCase()) ||
      user.email.toLowerCase().includes(query.toLowerCase())
    )
  }
  
  return mockUsers
}

const { data, loading, error, fetchData, refresh, hasData, hasError } = useApi(fetchUsers)

const searchQuery = ref('')

// 当搜索查询变化时自动搜索
watch(searchQuery, (newQuery) => {
  if (newQuery.trim() !== '') {
    searchUsers()
  }
})

const searchUsers = () => {
  fetchData(searchQuery.value)
}

const refreshData = () => {
  refresh()
}

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

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

.controls {
  margin-bottom: 20px;
  display: flex;
  gap: 10px;
  align-items: center;
}

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

.error {
  color: #ff4757;
}

.users {
  list-style: none;
  padding: 0;
}

.user-item {
  border: 1px solid #e0e0e0;
  margin-bottom: 10px;
  padding: 15px;
  border-radius: 4px;
}
</style>

高级组合式函数模式

我们还可以创建更复杂的组合式函数来处理特定的业务场景。以下是一个处理表单验证的组合式函数:

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

export function useFormValidation(initialForm = {}) {
  const form = ref({ ...initialForm })
  const errors = ref({})
  const isSubmitting = ref(false)
  
  // 验证规则
  const rules = ref({})
  
  // 设置验证规则
  const setRules = (newRules) => {
    rules.value = newRules
  }
  
  // 验证单个字段
  const validateField = (fieldName) => {
    const value = form.value[fieldName]
    const fieldRules = rules.value[fieldName] || []
    
    for (const rule of fieldRules) {
      if (!rule.validator(value)) {
        errors.value[fieldName] = rule.message
        return false
      }
    }
    
    delete errors.value[fieldName]
    return true
  }
  
  // 验证整个表单
  const validateForm = () => {
    let isValid = true
    errors.value = {}
    
    Object.keys(rules.value).forEach(fieldName => {
      if (!validateField(fieldName)) {
        isValid = false
      }
    })
    
    return isValid
  }
  
  // 设置表单值
  const setFieldValue = (fieldName, value) => {
    form.value[fieldName] = value
    // 实时验证
    validateField(fieldName)
  }
  
  // 重置表单
  const resetForm = () => {
    form.value = { ...initialForm }
    errors.value = {}
  }
  
  // 提交表单
  const submitForm = async (submitHandler) => {
    if (!validateForm()) {
      return false
    }
    
    isSubmitting.value = true
    
    try {
      await submitHandler(form.value)
      return true
    } catch (err) {
      console.error('提交失败:', err)
      return false
    } finally {
      isSubmitting.value = false
    }
  }
  
  // 表单是否有效
  const isValid = computed(() => {
    return Object.keys(errors.value).length === 0 && 
           Object.keys(form.value).every(key => form.value[key] !== '')
  })
  
  return {
    form,
    errors,
    isSubmitting,
    setRules,
    validateField,
    validateForm,
    setFieldValue,
    resetForm,
    submitForm,
    isValid
  }
}

状态管理最佳实践

响应式数据的性能优化

在处理大量响应式数据时,我们需要考虑性能优化:

// useOptimizedState.js
import { ref, shallowRef, computed, watch } from 'vue'

export function useOptimizedState(initialValue) {
  // 对于大型对象,使用 shallowRef 可以避免深度响应式追踪
  const state = shallowRef(initialValue)
  
  // 只在需要时才创建计算属性
  const computedValue = computed(() => {
    // 复杂的计算逻辑
    return state.value.data?.map(item => ({
      ...item,
      processed: true
    }))
  })
  
  // 使用 watch 的 flush 选项优化性能
  const watchEffectOptimized = (callback, options = {}) => {
    return watch(state, callback, {
      flush: options.flush || 'pre', // pre, post, sync
      deep: options.deep || false,
      immediate: options.immediate || false
    })
  }
  
  const updateState = (newData) => {
    state.value = newData
  }
  
  return {
    state,
    computedValue,
    watchEffectOptimized,
    updateState
  }
}

数据持久化处理

在实际应用中,我们经常需要将响应式数据持久化到本地存储:

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

export function usePersistence(key, initialValue) {
  const persisted = ref(initialValue)
  
  // 从本地存储加载数据
  const loadFromStorage = () => {
    try {
      const stored = localStorage.getItem(key)
      if (stored) {
        persisted.value = JSON.parse(stored)
      }
    } catch (error) {
      console.error(`Failed to load ${key} from localStorage`, error)
    }
  }
  
  // 保存到本地存储
  const saveToStorage = () => {
    try {
      localStorage.setItem(key, JSON.stringify(persisted.value))
    } catch (error) {
      console.error(`Failed to save ${key} to localStorage`, error)
    }
  }
  
  // 监听数据变化并保存
  watch(persisted, saveToStorage, { deep: true })
  
  // 初始化时加载数据
  loadFromStorage()
  
  return persisted
}

错误处理与调试

响应式数据的错误边界

在处理响应式数据时,我们需要考虑错误处理:

// useSafeRef.js
import { ref } from 'vue'

export function useSafeRef(initialValue) {
  const value = ref(initialValue)
  
  // 安全的值设置函数
  const safeSet = (newValue) => {
    try {
      value.value = newValue
    } catch (error) {
      console.error('Failed to set value:', error)
      // 可以选择恢复到之前的值或使用默认值
    }
  }
  
  // 安全的值获取函数
  const safeGet = () => {
    try {
      return value.value
    } catch (error) {
      console.error('Failed to get value:', error)
      return null
    }
  }
  
  return {
    value,
    safeSet,
    safeGet
  }
}

调试工具集成

Vue 3 提供了强大的调试工具支持:

// useDebug.js
import { watch, onMounted, onUnmounted } from 'vue'

export function useDebug(name, data) {
  // 在组件挂载时输出调试信息
  onMounted(() => {
    console.log(`[DEBUG] ${name} mounted`, data)
  })
  
  // 监听数据变化
  watch(data, (newVal, oldVal) => {
    console.log(`[DEBUG] ${name} changed`, { old: oldVal, new: newVal })
  }, { deep: true })
  
  // 在组件卸载时输出调试信息
  onUnmounted(() => {
    console.log(`[DEBUG] ${name} unmounted`)
  })
}

实际项目应用案例

完整的电商购物车系统

让我们通过一个完整的电商购物车系统来展示所有概念的实际应用:

<!-- ShoppingCart.vue -->
<template>
  <div class="shopping-cart">
    <h2>购物车</h2>
    
    <div v-if="loading" class="loading">加载中...</div>
    
    <div v-else-if="error" class="error">{{ error }}</div>
    
    <div v-else>
      <div class="cart-summary">
        <p>商品数量: {{ totalItems }}</p>
        <p>总价: ¥{{ totalPrice.toFixed(2) }}</p>
        <button @click="clearCart" :disabled="totalItems === 0">清空购物车</button>
      </div>
      
      <div class="cart-items">
        <div 
          v-for="item in items" 
          :key="item.id"
          class="cart-item"
        >
          <img :src="item.image" :alt="item.name" />
          <div class="item-details">
            <h4>{{ item.name }}</h4>
            <p>¥{{ item.price.toFixed(2) }}</p>
            <div class="quantity-controls">
              <button @click="updateQuantity(item.id, item.quantity - 1)">-</button>
              <span>{{ item.quantity }}</span>
              <button @click="updateQuantity(item.id, item.quantity + 1)">+</button>
            </div>
            <button @click="removeFromCart(item.id)" class="remove-btn">删除</button>
          </div>
        </div>
      </div>
      
      <div v-if="items.length === 0" class="empty-cart">
        购物车为空
      </div>
    </div>
  </div>
</template>

<script setup>
import { computed, onMounted } from 'vue'
import { useCartStore } from './stores/cartStore'

const { 
  items, 
  totalPrice, 
  totalItems, 
  addToCart, 
  removeFromCart, 
  updateQuantity, 
  clearCart,
  loading,
  error
} = useCartStore()

// 模拟初始化数据
onMounted(() => {
  // 这里可以加载持久化的购物车数据
})
</script>

<style scoped>
.shopping-cart {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.cart-summary {
  background: #f5f5f5;
  padding: 15px;
  border-radius: 4px;
  margin-bottom: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.loading, .error, .empty-cart {
  text-align: center;
  padding: 40px;
  font-size: 18px;
}

.error {
  color: #ff4757;
}

.cart-items {
  display: flex;
  flex-direction: column;
  gap: 15px;
}

.cart-item {
  display: flex;
  border: 1px solid #e0e0e0;
  border-radius: 4px;
  padding: 15px;
  align-items: center;
  gap: 15px;
}

.item-details {
  flex: 1;
}

.quantity-controls {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 10px 0;
}

.quantity-controls button {
  width: 30px;
  height: 30px;
  border: none;
  background: #007bff;
  color: white;
  border-radius: 4px;
  cursor: pointer;
}

.remove-btn {
  background: #ff4757;
  color: white;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
}
</style>

总结

Vue 3 Composition API 为我们提供了一种更加灵活和强大的组件开发方式。通过本文的实践演示,我们可以看到:

  1. 响应式数据管理:使用 refreactive 等函数可以轻松创建和管理复杂的响应式数据结构。

  2. 组件间通信:通过 props、emit、provide/inject 等机制,可以实现多种组件间通信模式。

  3. 逻辑复用:组合式函数让代码复用变得更加简单和直观,提高了开发效率。

  4. 状态管理最佳实践:合理的性能优化、错误处理和调试支持是构建稳定应用的关键。

  5. 实际项目应用:通过完整的购物车系统案例,展示了如何将所有概念整合到实际开发中。

掌握 Vue 3 Composition API 不仅能提高代码质量,还能让团队协作更加高效。建议在实际项目中积极采用这些最佳实践,逐步提升前端开发的技术水平和工程化能力。随着 Vue 生态的不断发展,Composition API 将继续发挥重要作用,为现代前端开发提供更强大的支持。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000