Vue3 Composition API实战:从组件通信到状态管理的完整应用方案

Hannah770
Hannah770 2026-03-10T00:03:05+08:00
0 0 0

引言

Vue 3 的发布为前端开发者带来了全新的开发体验,其中最引人注目的特性之一就是 Composition API。相比传统的 Options API,Composition API 提供了更灵活、更强大的代码组织方式,特别是在处理复杂组件逻辑时表现得尤为突出。本文将深入探讨 Vue3 Composition API 的核心特性和实际应用场景,从基础的响应式数据管理到复杂的组件通信和状态管理,为 Vue3 项目提供完整的开发指导。

Vue3 Composition API 核心概念

什么是 Composition API

Composition API 是 Vue 3 中引入的一种新的组件逻辑组织方式。它允许我们通过组合函数(composable)来组织和复用组件逻辑,而不是像 Options API 那样按照选项(data、methods、computed 等)来组织代码。

核心响应式 API

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

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

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

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

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

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

响应式数据管理

Ref 和 Reactive 的区别与使用场景

在 Vue3 中,refreactive 是两种不同的响应式数据创建方式:

// ref 适用于基本类型和对象的引用
const count = ref(0)
const message = ref('Hello')

// reactive 适用于复杂对象结构
const user = reactive({
  name: 'John',
  age: 30,
  address: {
    city: 'Beijing',
    street: 'Main Street'
  }
})

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

响应式数据的深层嵌套处理

对于深层嵌套的对象,Vue3 提供了更灵活的处理方式:

import { reactive, toRefs } from 'vue'

const state = reactive({
  user: {
    profile: {
      name: 'Alice',
      settings: {
        theme: 'dark',
        language: 'zh-CN'
      }
    }
  }
})

// 使用 toRefs 可以将响应式对象的属性转换为 ref
const { user } = toRefs(state)

// 这样在模板中可以直接使用 user.profile.name 而不需要 user.value.profile.name

watch 和 watchEffect 的深度应用

import { watch, watchEffect } from 'vue'

const count = ref(0)
const obj = reactive({ a: 1, b: 2 })

// 基本的 watch 使用
watch(count, (newVal, oldVal) => {
  console.log(`count changed: ${oldVal} -> ${newVal}`)
})

// watchEffect 会自动追踪依赖
watchEffect(() => {
  console.log(`obj.a = ${obj.a}, obj.b = ${obj.b}`)
})

// 深度监听对象变化
watch(obj, (newVal, oldVal) => {
  console.log('object changed:', newVal)
}, { deep: true })

// 监听多个源
watch([count, obj], ([newCount, newObj], [oldCount, oldObj]) => {
  console.log(`count: ${oldCount} -> ${newCount}`)
})

组件间通信实战

Props 和 Emit 的新用法

在 Composition API 中,props 和 emit 的使用方式更加灵活:

// ChildComponent.vue
import { defineProps, defineEmits } from 'vue'

const props = defineProps({
  title: {
    type: String,
    required: true
  },
  count: {
    type: Number,
    default: 0
  }
})

const emit = defineEmits(['update-count', 'submit'])

const handleClick = () => {
  emit('update-count', props.count + 1)
}

const handleSubmit = (data) => {
  emit('submit', data)
}

Provide 和 Inject 的组合使用

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

export default {
  setup() {
    const theme = ref('light')
    const user = ref({ name: 'John', role: 'admin' })
    
    // 提供数据给子组件
    provide('theme', theme)
    provide('user', user)
    
    return {
      theme,
      user
    }
  }
}

// Child.vue
import { inject } from 'vue'

export default {
  setup() {
    // 注入提供的数据
    const theme = inject('theme')
    const user = inject('user')
    
    return {
      theme,
      user
    }
  }
}

使用自定义 Hook 进行组件通信

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

// 使用自定义 Hook
import { useCounter } from '@/composables/useCounter'

export default {
  setup() {
    const counter = useCounter(10)
    
    return {
      ...counter
    }
  }
}

状态管理最佳实践

基于 Composition API 的状态管理方案

// stores/userStore.js
import { reactive, readonly } from 'vue'

const state = reactive({
  currentUser: null,
  isLoggedIn: false,
  token: ''
})

const login = (user, token) => {
  state.currentUser = user
  state.isLoggedIn = true
  state.token = token
}

const logout = () => {
  state.currentUser = null
  state.isLoggedIn = false
  state.token = ''
}

const updateProfile = (profile) => {
  if (state.currentUser) {
    Object.assign(state.currentUser, profile)
  }
}

// 使用 readonly 确保状态不可直接修改
export default {
  state: readonly(state),
  login,
  logout,
  updateProfile
}

复杂状态管理的模块化设计

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

// 用户状态管理
const userStore = (() => {
  const state = reactive({
    profile: null,
    permissions: [],
    preferences: {}
  })
  
  const setProfile = (profile) => {
    state.profile = profile
  }
  
  const setPermissions = (permissions) => {
    state.permissions = permissions
  }
  
  const updatePreferences = (preferences) => {
    Object.assign(state.preferences, preferences)
  }
  
  return {
    state: readonly(state),
    setProfile,
    setPermissions,
    updatePreferences
  }
})()

// 应用状态管理
const appStore = (() => {
  const state = reactive({
    loading: false,
    error: null,
    notifications: []
  })
  
  const setLoading = (loading) => {
    state.loading = loading
  }
  
  const setError = (error) => {
    state.error = error
  }
  
  const addNotification = (notification) => {
    state.notifications.push(notification)
  }
  
  return {
    state: readonly(state),
    setLoading,
    setError,
    addNotification
  }
})()

export default {
  user: userStore,
  app: appStore
}

状态持久化方案

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

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

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

自定义 Hook 开发

创建可复用的逻辑组合

// composables/useFetch.js
import { ref, reactive } 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
    }
  }
  
  return {
    data,
    loading,
    error,
    fetchData
  }
}

// 使用示例
export default {
  setup() {
    const { data, loading, error, fetchData } = useFetch('/api/users')
    
    fetchData()
    
    return {
      data,
      loading,
      error
    }
  }
}

高级自定义 Hook 实现

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

export function useDebounce(value, delay = 300) {
  const debouncedValue = ref(value)
  
  watch(
    value,
    (newValue) => {
      const handler = setTimeout(() => {
        debouncedValue.value = newValue
      }, delay)
      
      return () => clearTimeout(handler)
    },
    { immediate: true }
  )
  
  return debouncedValue
}

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

性能优化策略

计算属性的优化使用

// 避免在模板中进行复杂的计算
export default {
  setup() {
    const items = ref([])
    
    // 使用 computed 进行复杂计算
    const filteredItems = computed(() => {
      return items.value.filter(item => item.active)
    })
    
    const sortedItems = computed(() => {
      return [...filteredItems.value].sort((a, b) => a.name.localeCompare(b.name))
    })
    
    // 对于需要缓存的复杂计算,使用 memoization
    const expensiveCalculation = computed(() => {
      // 模拟耗时操作
      return items.value.reduce((acc, item) => {
        return acc + item.value * 2
      }, 0)
    })
    
    return {
      filteredItems,
      sortedItems,
      expensiveCalculation
    }
  }
}

组件渲染优化

// 使用 keep-alive 缓存组件状态
// <template>
//   <keep-alive>
//     <component :is="currentComponent" />
//   </keep-alive>
// </template>

// 使用 v-memo 进行条件渲染优化
export default {
  setup() {
    const items = ref([])
    
    return {
      items
    }
  },
  
  // 或者在模板中使用
  // <template>
  //   <div v-for="item in items" :key="item.id" v-memo="[item.id]">
  //     {{ item.name }}
  //   </div>
  // </template>
}

实际项目应用案例

完整的购物车组件实现

<template>
  <div class="shopping-cart">
    <h2>购物车 ({{ cartItems.length }})</h2>
    
    <div v-if="loading">加载中...</div>
    <div v-else-if="error">{{ error }}</div>
    <div v-else>
      <div 
        v-for="item in cartItems" 
        :key="item.id"
        class="cart-item"
      >
        <span>{{ item.name }}</span>
        <span>数量: {{ item.quantity }}</span>
        <span>小计: ¥{{ (item.price * item.quantity).toFixed(2) }}</span>
        <button @click="removeItem(item.id)">删除</button>
      </div>
      
      <div class="cart-total">
        <h3>总计: ¥{{ totalPrice.toFixed(2) }}</h3>
        <button @click="checkout">结算</button>
      </div>
    </div>
  </div>
</template>

<script>
import { ref, computed } from 'vue'
import { useFetch } from '@/composables/useFetch'

export default {
  name: 'ShoppingCart',
  setup() {
    const cartItems = ref([])
    const loading = ref(false)
    const error = ref(null)
    
    // 获取购物车数据
    const fetchCart = async () => {
      try {
        loading.value = true
        const response = await fetch('/api/cart')
        if (!response.ok) throw new Error('获取购物车失败')
        cartItems.value = await response.json()
      } catch (err) {
        error.value = err.message
      } finally {
        loading.value = false
      }
    }
    
    // 计算总价
    const totalPrice = computed(() => {
      return cartItems.value.reduce((total, item) => {
        return total + (item.price * item.quantity)
      }, 0)
    })
    
    // 删除商品
    const removeItem = async (itemId) => {
      try {
        await fetch(`/api/cart/${itemId}`, { method: 'DELETE' })
        cartItems.value = cartItems.value.filter(item => item.id !== itemId)
      } catch (err) {
        error.value = err.message
      }
    }
    
    // 结算
    const checkout = async () => {
      try {
        await fetch('/api/checkout', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ items: cartItems.value })
        })
        alert('结算成功!')
        cartItems.value = []
      } catch (err) {
        error.value = err.message
      }
    }
    
    // 初始化数据
    fetchCart()
    
    return {
      cartItems,
      loading,
      error,
      totalPrice,
      removeItem,
      checkout
    }
  }
}
</script>

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

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

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

多级组件通信实战

<!-- App.vue -->
<template>
  <div id="app">
    <Header :user="currentUser" @logout="handleLogout" />
    <MainContent :theme="currentTheme" />
    <Footer :version="appVersion" />
  </div>
</template>

<script>
import { ref, provide } from 'vue'
import Header from './components/Header.vue'
import MainContent from './components/MainContent.vue'
import Footer from './components/Footer.vue'

export default {
  name: 'App',
  components: {
    Header,
    MainContent,
    Footer
  },
  setup() {
    const currentUser = ref(null)
    const currentTheme = ref('light')
    const appVersion = ref('1.0.0')
    
    // 提供全局状态
    provide('currentUser', currentUser)
    provide('currentTheme', currentTheme)
    provide('appVersion', appVersion)
    
    const handleLogout = () => {
      currentUser.value = null
    }
    
    return {
      currentUser,
      currentTheme,
      appVersion,
      handleLogout
    }
  }
}
</script>

最佳实践总结

代码组织原则

  1. 单一职责原则:每个自定义 Hook 应该只负责一个特定的功能
  2. 可复用性:设计时要考虑组件间的通用逻辑复用
  3. 类型安全:使用 TypeScript 可以提供更好的开发体验
// 类型安全的自定义 Hook 示例
import { ref, computed } from 'vue'

export function useTypedCounter(initialValue = 0) {
  const count = ref<number>(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
  }
}

调试和测试

// 测试用的自定义 Hook
import { ref } from 'vue'

export function useTestableCounter(initialValue = 0) {
  const count = ref(initialValue)
  
  const increment = () => {
    count.value++
  }
  
  const decrement = () => {
    count.value--
  }
  
  // 可以在测试中直接访问
  const getCount = () => count.value
  
  return {
    count,
    increment,
    decrement,
    getCount
  }
}

结论

Vue3 Composition API 为前端开发带来了革命性的变化,它不仅提供了更灵活的代码组织方式,还让组件逻辑的复用变得更加简单。通过本文的详细介绍,我们看到了从基础响应式数据管理到复杂状态管理的完整应用方案。

在实际项目中,合理运用 Composition API 可以显著提升代码的可维护性和可读性。关键是要理解其核心概念,掌握最佳实践,并根据具体需求选择合适的模式。无论是简单的组件通信还是复杂的全局状态管理,Composition API 都能提供强大的支持。

随着 Vue3 生态系统的不断完善,我们有理由相信 Composition API 将成为现代前端开发的标准工具集,为开发者带来更加优雅和高效的开发体验。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000