Vue 3 Composition API实战:从组件通信到状态管理的完整解决方案

BraveWood
BraveWood 2026-02-06T00:14:12+08:00
0 0 1

引言

Vue 3 的发布带来了全新的 Composition API,这一创新性的 API 设计为开发者提供了更加灵活和强大的组件开发方式。相比于传统的 Options API,Composition API 将逻辑组织得更加自然,使得代码复用和维护变得更加容易。本文将深入探讨 Vue 3 Composition API 的各个方面,从基础概念到高级实践,涵盖组件通信、响应式数据管理、组合式函数封装等核心内容。

Vue 3 Composition API 基础概念

什么是 Composition API

Composition API 是 Vue 3 中引入的一种新的组件开发方式,它允许我们使用函数来组织和复用逻辑代码。与传统的 Options API(如 data、methods、computed 等)不同,Composition API 将相关的逻辑组合在一起,而不是按照属性类型分组。

核心响应式函数

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

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

// ref 用于创建响应式变量
const count = ref(0)
console.log(count.value) // 0

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

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

// watch 用于监听响应式数据变化
watch(count, (newValue, oldValue) => {
  console.log(`count changed from ${oldValue} to ${newValue}`)
})

组件通信实战

父子组件通信

在 Vue 3 中,父子组件通信可以通过 props 和 emit 来实现。使用 Composition API,我们可以更优雅地处理这些通信:

<!-- Parent.vue -->
<template>
  <div>
    <h2>Parent Component</h2>
    <Child 
      :message="parentMessage" 
      :user-info="userInfo"
      @child-event="handleChildEvent"
    />
    <p>Received from child: {{ childResponse }}</p>
  </div>
</template>

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

const parentMessage = ref('Hello from Parent')
const userInfo = reactive({
  name: 'John',
  age: 25
})

const childResponse = ref('')

const handleChildEvent = (data) => {
  childResponse.value = data
}
</script>
<!-- Child.vue -->
<template>
  <div>
    <h3>Child Component</h3>
    <p>{{ message }}</p>
    <p>User: {{ userInfo.name }}, Age: {{ userInfo.age }}</p>
    <button @click="sendToParent">Send to Parent</button>
  </div>
</template>

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

// 定义 props
const props = defineProps({
  message: {
    type: String,
    default: ''
  },
  userInfo: {
    type: Object,
    required: true
  }
})

// 定义 emits
const emit = defineEmits(['child-event'])

const sendToParent = () => {
  emit('child-event', `Response from child: ${props.message}`)
}
</script>

兄弟组件通信

兄弟组件之间的通信可以通过 EventBus 或者更现代的 Pinia 状态管理来实现。这里我们展示使用全局状态管理的方式:

<!-- ComponentA.vue -->
<template>
  <div>
    <h3>Component A</h3>
    <p>Shared Data: {{ sharedData }}</p>
    <button @click="updateSharedData">Update Data</button>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'
import { useSharedStore } from '@/stores/shared'

const store = useSharedStore()
const sharedData = ref(store.data)

watch(() => store.data, (newVal) => {
  sharedData.value = newVal
})

const updateSharedData = () => {
  store.updateData('Updated from Component A')
}
</script>

响应式数据管理

使用 ref 和 reactive

在 Composition API 中,ref 和 reactive 是两个核心的响应式函数:

import { ref, reactive } from 'vue'

// 使用 ref 创建响应式变量
const count = ref(0)
const name = ref('Vue')
const isVisible = ref(false)

// 使用 reactive 创建响应式对象
const user = reactive({
  id: 1,
  name: 'John',
  email: 'john@example.com',
  profile: {
    avatar: '',
    bio: ''
  }
})

// 修改值的方式
count.value = 10
name.value = 'Vue 3'
user.name = 'Jane' // 对于 reactive 对象,直接修改属性即可
user.profile.avatar = 'avatar.jpg'

计算属性和监听器

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

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

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

// 带 setter 的计算属性
const reversedName = computed({
  get: () => {
    return firstName.value.split('').reverse().join('')
  },
  set: (value) => {
    const names = value.split(' ')
    firstName.value = names[0]
    lastName.value = names[1]
  }
})

// 监听器
const count = ref(0)
const doubleCount = computed(() => count.value * 2)

// 监听单个值
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}`)
})

// watchEffect 自动追踪依赖
watchEffect(() => {
  console.log(`Full name is: ${fullName.value}`)
})

组合式函数封装

创建可复用的组合式函数

组合式函数是 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
  }
}
// composables/useApi.js
import { ref, reactive } from 'vue'

export function useApi() {
  const loading = ref(false)
  const error = ref(null)
  const data = ref(null)
  
  const fetchData = async (url) => {
    try {
      loading.value = true
      error.value = null
      const response = await fetch(url)
      data.value = await response.json()
    } catch (err) {
      error.value = err.message
      console.error('API Error:', err)
    } finally {
      loading.value = false
    }
  }
  
  return {
    loading,
    error,
    data,
    fetchData
  }
}
<!-- 使用组合式函数的组件 -->
<template>
  <div>
    <h2>Counter Component</h2>
    <p>Count: {{ count }}</p>
    <p>Double: {{ doubleCount }}</p>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="reset">Reset</button>
  </div>
</template>

<script setup>
import { useCounter } from '@/composables/useCounter'

const { count, increment, decrement, reset, doubleCount } = useCounter(0)
</script>

高级组合式函数示例

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

export function useLocalStorage(key, defaultValue) {
  const storedValue = localStorage.getItem(key)
  const value = ref(storedValue ? JSON.parse(storedValue) : defaultValue)
  
  watch(value, (newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue))
  }, { deep: true })
  
  return value
}
// composables/useWindowResize.js
import { ref, onMounted, onUnmounted } from 'vue'

export function useWindowResize() {
  const windowWidth = ref(window.innerWidth)
  const windowHeight = ref(window.innerHeight)
  
  const handleResize = () => {
    windowWidth.value = window.innerWidth
    windowHeight.value = window.innerHeight
  }
  
  onMounted(() => {
    window.addEventListener('resize', handleResize)
  })
  
  onUnmounted(() => {
    window.removeEventListener('resize', handleResize)
  })
  
  return {
    windowWidth,
    windowHeight
  }
}

状态管理最佳实践

使用 Pinia 进行状态管理

虽然 Composition API 提供了强大的响应式能力,但在复杂应用中,状态管理仍然是一个挑战。Pinia 是 Vue 3 推荐的状态管理库:

// stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  const user = ref(null)
  const isLoggedIn = computed(() => !!user.value)
  
  const login = (userData) => {
    user.value = userData
  }
  
  const logout = () => {
    user.value = null
  }
  
  const updateProfile = (profileData) => {
    if (user.value) {
      user.value.profile = { ...user.value.profile, ...profileData }
    }
  }
  
  return {
    user,
    isLoggedIn,
    login,
    logout,
    updateProfile
  }
})
<!-- UserComponent.vue -->
<template>
  <div>
    <div v-if="isLoggedIn">
      <h2>Welcome, {{ user?.name }}!</h2>
      <button @click="logout">Logout</button>
    </div>
    <div v-else>
      <h2>Please login</h2>
      <button @click="login">Login</button>
    </div>
  </div>
</template>

<script setup>
import { useUserStore } from '@/stores/user'
import { onMounted } from 'vue'

const store = useUserStore()

const { user, isLoggedIn, login, logout } = store

const login = () => {
  store.login({
    id: 1,
    name: 'John Doe',
    email: 'john@example.com',
    profile: {
      avatar: 'avatar.jpg',
      bio: 'Vue developer'
    }
  })
}
</script>

多状态管理模块

// stores/index.js
import { defineStore } from 'pinia'

export const useMainStore = defineStore('main', () => {
  const theme = ref('light')
  const language = ref('en')
  
  const toggleTheme = () => {
    theme.value = theme.value === 'light' ? 'dark' : 'light'
  }
  
  return {
    theme,
    language,
    toggleTheme
  }
})

// stores/products.js
import { defineStore } from 'pinia'

export const useProductStore = defineStore('products', () => {
  const products = ref([])
  const loading = ref(false)
  
  const fetchProducts = async () => {
    try {
      loading.value = true
      const response = await fetch('/api/products')
      products.value = await response.json()
    } catch (error) {
      console.error('Failed to fetch products:', error)
    } finally {
      loading.value = false
    }
  }
  
  const addProduct = (product) => {
    products.value.push(product)
  }
  
  return {
    products,
    loading,
    fetchProducts,
    addProduct
  }
})

高级特性与最佳实践

异步数据处理

<script setup>
import { ref, onMounted } from 'vue'

const data = ref(null)
const loading = ref(false)
const error = ref(null)

const fetchData = async () => {
  try {
    loading.value = true
    error.value = null
    
    const response = await fetch('/api/data')
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }
    
    data.value = await response.json()
  } catch (err) {
    error.value = err.message
    console.error('Fetch error:', err)
  } finally {
    loading.value = false
  }
}

onMounted(() => {
  fetchData()
})
</script>

条件渲染与动态组件

<script setup>
import { ref, computed } from 'vue'

const currentComponent = ref('ComponentA')
const showComponent = ref(true)

const components = {
  ComponentA: () => import('./ComponentA.vue'),
  ComponentB: () => import('./ComponentB.vue')
}

const dynamicComponent = computed(() => {
  return showComponent.value ? components[currentComponent.value] : null
})
</script>

<template>
  <div>
    <button @click="currentComponent = 'ComponentA'">Show A</button>
    <button @click="currentComponent = 'ComponentB'">Show B</button>
    <button @click="showComponent = !showComponent">Toggle</button>
    
    <component :is="dynamicComponent" v-if="dynamicComponent" />
  </div>
</template>

性能优化技巧

// 使用 useMemo 避免不必要的计算
import { computed, ref } from 'vue'

const items = ref([])
const searchTerm = ref('')

// 避免在每次渲染时都重新计算过滤结果
const filteredItems = computed(() => {
  if (!searchTerm.value) return items.value
  return items.value.filter(item => 
    item.name.toLowerCase().includes(searchTerm.value.toLowerCase())
  )
})

// 使用防抖函数优化搜索
import { debounce } from 'lodash'

const debouncedSearch = debounce((term) => {
  searchTerm.value = term
}, 300)

实际项目应用案例

完整的购物车功能实现

<!-- ShoppingCart.vue -->
<template>
  <div class="shopping-cart">
    <h2>Shopping Cart</h2>
    
    <div v-if="loading">Loading...</div>
    <div v-else-if="error">{{ error }}</div>
    <div v-else>
      <div 
        v-for="item in cartItems" 
        :key="item.id"
        class="cart-item"
      >
        <h3>{{ item.name }}</h3>
        <p>Price: ${{ item.price }}</p>
        <p>Quantity: {{ item.quantity }}</p>
        <button @click="removeItem(item.id)">Remove</button>
      </div>
      
      <div class="cart-summary">
        <h3>Total: ${{ totalAmount }}</h3>
        <button @click="checkout">Checkout</button>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'
import { useCartStore } from '@/stores/cart'

const store = useCartStore()
const loading = ref(false)
const error = ref(null)

const cartItems = computed(() => store.items)
const totalAmount = computed(() => {
  return store.items.reduce((total, item) => {
    return total + (item.price * item.quantity)
  }, 0)
})

const removeItem = (id) => {
  store.removeItem(id)
}

const checkout = () => {
  // 实现结账逻辑
  console.log('Checkout:', cartItems.value)
  store.clearCart()
}

onMounted(() => {
  // 初始化购物车数据
  store.loadCart()
})
</script>

<style scoped>
.shopping-cart {
  padding: 20px;
}

.cart-item {
  border: 1px solid #ccc;
  margin: 10px 0;
  padding: 15px;
}

.cart-summary {
  margin-top: 20px;
  padding: 20px;
  border-top: 2px solid #000;
}
</style>
// stores/cart.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCartStore = defineStore('cart', () => {
  const items = ref([])
  const loading = ref(false)
  
  const totalItems = computed(() => items.value.reduce((total, item) => total + item.quantity, 0))
  
  const loadCart = async () => {
    try {
      loading.value = true
      const response = await fetch('/api/cart')
      items.value = await response.json()
    } catch (error) {
      console.error('Failed to load cart:', error)
    } finally {
      loading.value = false
    }
  }
  
  const addItem = (item) => {
    const existingItem = items.value.find(i => i.id === item.id)
    if (existingItem) {
      existingItem.quantity += item.quantity
    } else {
      items.value.push(item)
    }
  }
  
  const removeItem = (id) => {
    items.value = items.value.filter(item => item.id !== id)
  }
  
  const updateQuantity = (id, quantity) => {
    const item = items.value.find(i => i.id === id)
    if (item) {
      item.quantity = quantity
    }
  }
  
  const clearCart = () => {
    items.value = []
  }
  
  return {
    items,
    loading,
    totalItems,
    loadCart,
    addItem,
    removeItem,
    updateQuantity,
    clearCart
  }
})

总结

Vue 3 的 Composition API 为前端开发带来了革命性的变化,它不仅提供了更灵活的代码组织方式,还让逻辑复用变得更加简单。通过本文的介绍,我们深入了解了:

  1. 基础概念:理解 ref、reactive、computed、watch 等核心响应式函数的使用
  2. 组件通信:掌握父子组件、兄弟组件之间的通信机制
  3. 状态管理:学习如何使用 Pinia 进行复杂应用的状态管理
  4. 组合式函数:学会封装可复用的逻辑代码
  5. 最佳实践:掌握性能优化和实际项目应用技巧

随着 Vue 3 的普及,Composition API 已经成为现代 Vue 开发的标准方式。它不仅提高了代码的可维护性,还使得团队协作变得更加高效。通过合理使用 Composition API,我们可以构建出更加健壮、可扩展的 Vue 应用程序。

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

  • 简单组件可以继续使用 Options API
  • 复杂逻辑需要复用时,优先考虑 Composition API
  • 团队协作时,建立统一的组合式函数规范

Vue 3 的 Composition API 为我们打开了新的可能性,让我们能够以更加优雅的方式构建现代前端应用。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000