Vue 3 Composition API实战:构建响应式状态管理与组件通信

GoodKyle
GoodKyle 2026-02-07T19:02:10+08:00
0 0 0

引言

Vue 3 的发布带来了全新的 Composition API,这不仅是对 Vue 2.x Options API 的补充,更是一次重大的架构革新。Composition API 通过将逻辑代码按功能组织,让开发者能够更好地复用和维护复杂的业务逻辑。本文将深入探讨如何使用 Vue 3 Composition API 构建响应式状态管理与组件通信系统,通过实际项目案例展示现代 Vue 应用的构建方法。

Vue 3 Composition API 核心概念

什么是 Composition API

Composition API 是 Vue 3 中引入的一种新的组件逻辑组织方式。它允许我们将组件中的逻辑按照功能模块进行组织,而不是传统的按选项分类(data、methods、computed、watch 等)。这种模式让代码更加灵活,易于维护和复用。

// Vue 2 Options API 示例
export default {
  data() {
    return {
      count: 0,
      name: ''
    }
  },
  computed: {
    reversedName() {
      return this.name.split('').reverse().join('')
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  watch: {
    count(newVal, oldVal) {
      console.log(`count changed from ${oldVal} to ${newVal}`)
    }
  }
}
// Vue 3 Composition API 示例
import { ref, computed, watch } from 'vue'

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

响应式系统的核心特性

Vue 3 的响应式系统基于 ES6 的 Proxy 和 Reflect 实现,提供了更强大和灵活的响应式能力:

  1. ref(): 创建响应式的单值引用
  2. reactive(): 创建响应式的对象
  3. computed(): 创建计算属性
  4. watch(): 监听响应式数据变化

响应式状态管理实践

基础响应式数据操作

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

import { ref, reactive } from 'vue'

export default {
  setup() {
    // 创建基本类型响应式数据
    const count = ref(0)
    const message = ref('Hello Vue 3')
    
    // 创建对象响应式数据
    const user = reactive({
      name: 'John',
      age: 25,
      email: 'john@example.com'
    })
    
    // 修改数据
    const increment = () => {
      count.value++
    }
    
    const updateUserInfo = () => {
      user.name = 'Jane'
      user.age = 30
    }
    
    return {
      count,
      message,
      user,
      increment,
      updateUserInfo
    }
  }
}

复杂对象的响应式处理

对于嵌套对象和数组,Vue 3 提供了完整的响应式支持:

import { ref, reactive } from 'vue'

export default {
  setup() {
    // 嵌套对象响应式
    const userInfo = reactive({
      profile: {
        name: 'John',
        age: 25,
        address: {
          city: 'Beijing',
          country: 'China'
        }
      },
      hobbies: ['reading', 'swimming', 'coding']
    })
    
    // 数组响应式操作
    const addHobby = (hobby) => {
      userInfo.hobbies.push(hobby)
    }
    
    const removeHobby = (index) => {
      userInfo.hobbies.splice(index, 1)
    }
    
    // 嵌套对象更新
    const updateAddress = (newCity) => {
      userInfo.profile.address.city = newCity
    }
    
    return {
      userInfo,
      addHobby,
      removeHobby,
      updateAddress
    }
  }
}

自定义响应式组合函数

通过创建自定义的组合函数,我们可以将复杂的逻辑封装起来,实现更好的复用:

// 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
  }
}

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

export function useLocalStorage(key, defaultValue) {
  const value = ref(defaultValue)
  
  // 从 localStorage 初始化值
  const storedValue = localStorage.getItem(key)
  if (storedValue) {
    try {
      value.value = JSON.parse(storedValue)
    } catch (error) {
      console.error('Failed to parse localStorage value:', error)
    }
  }
  
  // 监听变化并同步到 localStorage
  watch(value, (newValue) => {
    try {
      localStorage.setItem(key, JSON.stringify(newValue))
    } catch (error) {
      console.error('Failed to save to localStorage:', error)
    }
  }, { deep: true })
  
  return value
}

组件间通信机制

Props 和 Emit 通信模式

在 Composition API 中,props 和 emit 的使用方式与 Options API 基本一致:

// Parent.vue
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

export default {
  components: {
    ChildComponent
  },
  setup() {
    const message = ref('Hello from parent')
    const user = ref({ name: 'John', age: 25 })
    
    const handleChildEvent = (data) => {
      console.log('Received from child:', data)
    }
    
    return {
      message,
      user,
      handleChildEvent
    }
  }
}
<!-- Parent.vue -->
<template>
  <div>
    <child-component 
      :message="message"
      :user="user"
      @child-event="handleChildEvent"
    />
  </div>
</template>
// ChildComponent.vue
export default {
  props: {
    message: {
      type: String,
      required: true
    },
    user: {
      type: Object,
      required: true
    }
  },
  setup(props, { emit }) {
    const handleClick = () => {
      emit('child-event', {
        message: 'Hello from child',
        timestamp: Date.now()
      })
    }
    
    return {
      handleClick
    }
  }
}

Provide / Inject 依赖注入

Provide / Inject 是 Vue 3 中强大的跨层级组件通信机制:

// Parent.vue
import { provide, ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

export default {
  components: {
    ChildComponent
  },
  setup() {
    const theme = ref('dark')
    const user = ref({ name: 'John', role: 'admin' })
    
    // 提供数据给子组件
    provide('theme', theme)
    provide('user', user)
    
    const toggleTheme = () => {
      theme.value = theme.value === 'dark' ? 'light' : 'dark'
    }
    
    return {
      toggleTheme
    }
  }
}
// ChildComponent.vue
import { inject } from 'vue'

export default {
  setup() {
    // 注入提供的数据
    const theme = inject('theme')
    const user = inject('user')
    
    const updateUserInfo = () => {
      user.value.name = 'Jane'
    }
    
    return {
      theme,
      user,
      updateUserInfo
    }
  }
}

全局状态管理

结合 Composition API 和响应式系统,我们可以构建轻量级的全局状态管理系统:

// store/index.js
import { reactive, readonly } from 'vue'

// 创建全局状态
const state = reactive({
  user: null,
  isLoggedIn: false,
  theme: 'light',
  notifications: []
})

// 创建状态操作方法
const setUser = (user) => {
  state.user = user
  state.isLoggedIn = !!user
}

const setTheme = (theme) => {
  state.theme = theme
}

const addNotification = (notification) => {
  state.notifications.push({
    id: Date.now(),
    ...notification,
    timestamp: new Date()
  })
}

const removeNotification = (id) => {
  const index = state.notifications.findIndex(n => n.id === id)
  if (index > -1) {
    state.notifications.splice(index, 1)
  }
}

// 创建只读状态访问器
const getters = {
  getUser: () => state.user,
  getIsLoggedIn: () => state.isLoggedIn,
  getTheme: () => state.theme,
  getNotifications: () => state.notifications
}

export default {
  state: readonly(state),
  setUser,
  setTheme,
  addNotification,
  removeNotification,
  ...getters
}
// composables/useGlobalStore.js
import store from '@/store'

export function useGlobalStore() {
  const { 
    state, 
    setUser, 
    setTheme, 
    addNotification, 
    removeNotification 
  } = store
  
  return {
    // 状态访问
    user: state.user,
    isLoggedIn: state.isLoggedIn,
    theme: state.theme,
    notifications: state.notifications,
    
    // 状态操作
    setUser,
    setTheme,
    addNotification,
    removeNotification
  }
}

高级响应式编程技巧

响应式数据的深度监听

Vue 3 的响应式系统支持深层监听,这对于复杂的嵌套对象非常有用:

import { ref, watch } from 'vue'

export default {
  setup() {
    const complexData = ref({
      users: [
        { id: 1, name: 'John', profile: { age: 25, city: 'Beijing' } },
        { id: 2, name: 'Jane', profile: { age: 30, city: 'Shanghai' } }
      ],
      settings: {
        theme: 'dark',
        language: 'zh-CN'
      }
    })
    
    // 深度监听整个对象
    watch(complexData, (newVal, oldVal) => {
      console.log('Complex data changed:', newVal)
    }, { deep: true })
    
    // 监听特定属性
    watch(() => complexData.value.users, (newUsers, oldUsers) => {
      console.log('Users array changed:', newUsers)
    }, { deep: true })
    
    // 监听嵌套属性
    watch(() => complexData.value.settings.theme, (newTheme, oldTheme) => {
      console.log(`Theme changed from ${oldTheme} to ${newTheme}`)
    })
    
    return {
      complexData
    }
  }
}

计算属性的优化

计算属性在 Composition API 中提供了更好的性能和灵活性:

import { ref, computed } from 'vue'

export default {
  setup() {
    const items = ref([])
    const filterText = ref('')
    
    // 基础计算属性
    const filteredItems = computed(() => {
      return items.value.filter(item => 
        item.name.toLowerCase().includes(filterText.value.toLowerCase())
      )
    })
    
    // 带有 getter 和 setter 的计算属性
    const fullName = computed({
      get: () => {
        return `${firstName.value} ${lastName.value}`
      },
      set: (value) => {
        const names = value.split(' ')
        firstName.value = names[0]
        lastName.value = names[1] || ''
      }
    })
    
    // 高性能计算属性
    const expensiveComputed = computed(() => {
      // 模拟耗时操作
      let result = 0
      for (let i = 0; i < 1000000; i++) {
        result += Math.sqrt(i)
      }
      return result
    })
    
    return {
      items,
      filterText,
      filteredItems,
      fullName,
      expensiveComputed
    }
  }
}

异步数据处理

在实际应用中,经常需要处理异步数据加载和状态管理:

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

export default {
  setup() {
    const data = ref(null)
    const loading = ref(false)
    const error = ref(null)
    
    // 模拟 API 调用
    const fetchData = async () => {
      try {
        loading.value = true
        error.value = null
        
        // 模拟网络请求延迟
        await new Promise(resolve => setTimeout(resolve, 1000))
        
        data.value = {
          id: 1,
          name: 'Sample Data',
          items: ['item1', 'item2', 'item3']
        }
      } 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 构建响应式状态管理:

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

export function useCart() {
  const items = ref([])
  
  // 添加商品到购物车
  const addItem = (product) => {
    const existingItem = items.value.find(item => item.id === product.id)
    
    if (existingItem) {
      existingItem.quantity += 1
    } else {
      items.value.push({
        ...product,
        quantity: 1
      })
    }
  }
  
  // 移除商品
  const removeItem = (productId) => {
    const index = items.value.findIndex(item => item.id === productId)
    if (index > -1) {
      items.value.splice(index, 1)
    }
  }
  
  // 更新商品数量
  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) {
        removeItem(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,
    addItem,
    removeItem,
    updateQuantity,
    totalPrice,
    totalItems,
    clearCart
  }
}
// components/Cart.vue
<template>
  <div class="cart">
    <h2>购物车</h2>
    
    <div v-if="totalItems === 0" class="empty-cart">
      购物车为空
    </div>
    
    <div v-else>
      <div 
        v-for="item in items" 
        :key="item.id"
        class="cart-item"
      >
        <div class="item-info">
          <h3>{{ item.name }}</h3>
          <p>价格: ¥{{ item.price }}</p>
        </div>
        
        <div class="item-controls">
          <button @click="updateQuantity(item.id, item.quantity - 1)">-</button>
          <span class="quantity">{{ item.quantity }}</span>
          <button @click="updateQuantity(item.id, item.quantity + 1)">+</button>
          <button @click="removeItem(item.id)" class="remove-btn">删除</button>
        </div>
      </div>
      
      <div class="cart-summary">
        <p>商品总数: {{ totalItems }}</p>
        <p>总价: ¥{{ totalPrice.toFixed(2) }}</p>
        <button @click="clearCart" class="clear-btn">清空购物车</button>
      </div>
    </div>
  </div>
</template>

<script>
import { useCart } from '@/composables/useCart'

export default {
  name: 'Cart',
  setup() {
    const { 
      items, 
      addItem, 
      removeItem, 
      updateQuantity, 
      totalPrice, 
      totalItems,
      clearCart
    } = useCart()
    
    return {
      items,
      addItem,
      removeItem,
      updateQuantity,
      totalPrice,
      totalItems,
      clearCart
    }
  }
}
</script>

<style scoped>
.cart {
  padding: 20px;
  border: 1px solid #ddd;
  border-radius: 8px;
}

.empty-cart {
  text-align: center;
  color: #999;
}

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

.item-controls {
  display: flex;
  align-items: center;
  gap: 10px;
}

.quantity {
  min-width: 30px;
  text-align: center;
}

.remove-btn, .clear-btn {
  background-color: #ff4757;
  color: white;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
}

.clear-btn {
  background-color: #ff6b81;
}

.cart-summary {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 2px solid #eee;
}
</style>

最佳实践与性能优化

组合函数的设计原则

良好的组合函数应该具备以下特点:

// ✅ 好的组合函数设计
export function useApi(url) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)
  
  const fetch = async () => {
    try {
      loading.value = true
      error.value = null
      
      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
    }
  }
  
  // 可以选择性地暴露一些方法
  const refresh = () => fetch()
  
  return {
    data,
    loading,
    error,
    fetch,
    refresh
  }
}

// ❌ 不好的设计
export function useApi(url) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)
  
  // 过多的暴露方法
  const fetch = async () => { /* ... */ }
  const getData = () => data.value
  const setLoading = (value) => loading.value = value
  const setError = (value) => error.value = value
  
  return {
    data,
    loading,
    error,
    fetch,
    getData,
    setLoading,
    setError
  }
}

性能优化策略

  1. 避免不必要的计算: 使用 computed 来缓存昂贵的计算结果
  2. 合理使用 watch: 避免监听不需要的数据
  3. 组件级别的优化: 使用 defineAsyncComponent 进行懒加载
import { ref, computed, watch, defineAsyncComponent } from 'vue'

export default {
  setup() {
    const data = ref([])
    
    // 优化的计算属性
    const filteredData = computed(() => {
      // 只在依赖变化时重新计算
      return data.value.filter(item => item.visible)
    })
    
    // 避免在渲染过程中执行昂贵操作
    const expensiveOperation = computed(() => {
      // 这个计算只会在需要时执行
      return data.value.reduce((acc, item) => acc + item.value, 0)
    })
    
    // 智能监听
    watch(data, (newData) => {
      // 只在数据真正改变时执行
      console.log('Data changed:', newData.length)
    }, { deep: true })
    
    // 异步组件懒加载
    const AsyncComponent = defineAsyncComponent(() => 
      import('./HeavyComponent.vue')
    )
    
    return {
      data,
      filteredData,
      expensiveOperation,
      AsyncComponent
    }
  }
}

总结

Vue 3 Composition API 为前端开发带来了更灵活、更强大的状态管理和组件通信能力。通过本文的介绍,我们了解了:

  1. 响应式数据管理: 使用 refreactive 等 API 创建和操作响应式数据
  2. 组件通信机制: Props、Emit、Provide/Inject 等多种通信方式的应用
  3. 组合函数设计: 如何封装可复用的逻辑代码
  4. 实际项目应用: 通过购物车应用展示了完整的开发流程
  5. 最佳实践: 性能优化和设计原则

Composition API 的核心优势在于它让我们能够更自由地组织代码逻辑,将相关的功能组合在一起,而不是被传统的选项分类所限制。这使得大型应用的维护变得更加容易,也提高了代码的可读性和复用性。

在实际开发中,建议根据项目需求选择合适的模式:

  • 简单组件可以使用 Options API
  • 复杂逻辑推荐使用 Composition API
  • 可以混合使用两种 API 来发挥各自优势

通过持续实践和优化,Vue 3 Composition API 将帮助我们构建更加现代化、高效的前端应用。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000