第76集:Store间通信与组合

概述

在复杂的Vue应用中,多个Store之间的通信和组合是构建可维护状态管理系统的关键。Pinia提供了灵活的Store间通信机制,支持直接调用、依赖注入和组合式设计。掌握Store间通信与组合的最佳实践对于构建大型应用至关重要。

核心知识点

1. Store间通信方式

1.1 直接调用方式

最直接的Store间通信方式是在一个Store中直接调用另一个Store:

// stores/product.ts
export const useProductStore = defineStore('product', {
  state: () => ({
    products: []
  }),
  
  actions: {
    async fetchProducts() {
      const response = await fetch('https://api.example.com/products')
      this.products = await response.json()
    }
  }
})

// stores/cart.ts
import { defineStore } from 'pinia'
import { useProductStore } from './product'

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: []
  }),
  
  actions: {
    async addToCart(productId: number, quantity: number = 1) {
      // 获取产品Store实例
      const productStore = useProductStore()
      
      // 确保产品数据已加载
      if (productStore.products.length === 0) {
        await productStore.fetchProducts()
      }
      
      // 查找产品
      const product = productStore.products.find(p => p.id === productId)
      if (product) {
        // 添加到购物车
        this.items.push({ productId, quantity, product })
      }
    }
  }
})

1.2 依赖注入方式

在Composition API风格中,可以使用依赖注入的方式组合Store:

// stores/user.ts
export const useUserStore = defineStore('user', () => {
  const user = ref(null)
  
  async function fetchUser() {
    const response = await fetch('https://api.example.com/user')
    user.value = await response.json()
  }
  
  return {
    user,
    fetchUser
  }
})

// stores/order.ts
export const useOrderStore = defineStore('order', () => {
  const orders = ref([])
  const loading = ref(false)
  
  // 直接调用其他Store
  const userStore = useUserStore()
  
  async function fetchOrders() {
    loading.value = true
    try {
      // 使用用户Store的数据
      if (!userStore.user) {
        await userStore.fetchUser()
      }
      
      const response = await fetch(`https://api.example.com/orders?userId=${userStore.user.id}`)
      orders.value = await response.json()
    } finally {
      loading.value = false
    }
  }
  
  return {
    orders,
    loading,
    fetchOrders
  }
})

2. Store组合策略

2.1 水平组合

水平组合是指将多个功能相关的Store组合在一起,形成一个更高级别的Store:

// stores/combined.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'
import { useProductStore } from './product'
import { useCartStore } from './cart'

export const useCombinedStore = defineStore('combined', () => {
  // 获取各个Store实例
  const userStore = useUserStore()
  const productStore = useProductStore()
  const cartStore = useCartStore()
  
  // 组合后的状态
  const isLoading = computed(() => {
    return userStore.loading || productStore.loading || cartStore.loading
  })
  
  const totalItems = computed(() => {
    return cartStore.items.reduce((sum, item) => sum + item.quantity, 0)
  })
  
  // 组合后的actions
  async function initializeApp() {
    await Promise.all([
      userStore.fetchUser(),
      productStore.fetchProducts()
    ])
  }
  
  return {
    // 暴露状态
    user: userStore.user,
    products: productStore.products,
    cartItems: cartStore.items,
    isLoading,
    totalItems,
    
    // 暴露actions
    initializeApp,
    addToCart: cartStore.addToCart,
    fetchOrders: userStore.fetchOrders
  }
})

2.2 垂直组合

垂直组合是指一个Store依赖于另一个Store,形成父子关系:

// stores/base.ts
export const useBaseStore = defineStore('base', () => {
  const loading = ref(false)
  const error = ref<string | null>(null)
  
  // 基础的API调用方法
  async function apiCall<T>(url: string, options: RequestInit = {}): Promise<T> {
    loading.value = true
    error.value = null
    
    try {
      const response = await fetch(url, {
        headers: {
          'Content-Type': 'application/json'
        },
        ...options
      })
      
      if (!response.ok) {
        throw new Error('API request failed')
      }
      
      return response.json()
    } catch (err) {
      error.value = err instanceof Error ? err.message : 'An unknown error occurred'
      throw err
    } finally {
      loading.value = false
    }
  }
  
  return {
    loading,
    error,
    apiCall
  }
})

// stores/user.ts
export const useUserStore = defineStore('user', () => {
  const users = ref<User[]>([])
  
  // 依赖基础Store
  const baseStore = useBaseStore()
  
  async function fetchUsers() {
    const data = await baseStore.apiCall<User[]>('https://api.example.com/users')
    users.value = data
  }
  
  return {
    users,
    loading: baseStore.loading,
    error: baseStore.error,
    fetchUsers
  }
})

3. Store间事件通信

对于复杂的Store间通信,可以使用事件总线或发布-订阅模式:

// stores/eventBus.ts
import { ref, computed } from 'vue'

type EventHandler = (...args: any[]) => void

export const useEventBus = defineStore('eventBus', () => {
  const events = ref<Map<string, EventHandler[]>>(new Map())
  
  // 订阅事件
  function on(eventName: string, handler: EventHandler) {
    if (!events.value.has(eventName)) {
      events.value.set(eventName, [])
    }
    events.value.get(eventName)?.push(handler)
  }
  
  // 发布事件
  function emit(eventName: string, ...args: any[]) {
    const handlers = events.value.get(eventName)
    if (handlers) {
      handlers.forEach(handler => handler(...args))
    }
  }
  
  // 取消订阅
  function off(eventName: string, handler?: EventHandler) {
    const handlers = events.value.get(eventName)
    if (handlers) {
      if (handler) {
        events.value.set(eventName, handlers.filter(h => h !== handler))
      } else {
        events.value.delete(eventName)
      }
    }
  }
  
  return {
    on,
    emit,
    off
  }
})

// 使用示例
// stores/user.ts
export const useUserStore = defineStore('user', () => {
  const eventBus = useEventBus()
  
  // 订阅事件
  eventBus.on('userLoggedIn', (user) => {
    console.log('User logged in:', user)
    // 处理登录事件
  })
  
  return {
    // ...
  }
})

// stores/auth.ts
export const useAuthStore = defineStore('auth', () => {
  const eventBus = useEventBus()
  
  async function login(email: string, password: string) {
    // 登录逻辑
    const user = await apiCall({ email, password })
    
    // 发布事件
    eventBus.emit('userLoggedIn', user)
    
    return user
  }
  
  return {
    login
  }
})

4. Store组合的最佳实践

4.1 避免循环依赖

循环依赖是Store组合中的常见问题,会导致初始化错误:

// ❌ 错误:循环依赖
// storeA.ts
export const useStoreA = defineStore('storeA', () => {
  const storeB = useStoreB() // 依赖storeB
  
  return {
    // ...
  }
})

// storeB.ts
export const useStoreB = defineStore('storeB', () => {
  const storeA = useStoreA() // 依赖storeA
  
  return {
    // ...
  }
})

解决方案

  • 重构Store,提取公共逻辑到第三个Store
  • 在运行时延迟获取依赖的Store
  • 使用事件总线解耦Store间通信

4.2 延迟获取Store

可以在需要时才获取依赖的Store,避免循环依赖:

// ✅ 正确:延迟获取Store
export const useStoreA = defineStore('storeA', () => {
  async function doSomething() {
    // 在需要时才获取storeB
    const storeB = useStoreB()
    await storeB.someAction()
  }
  
  return {
    doSomething
  }
})

4.3 保持Store的独立性

每个Store应该保持相对独立,只负责自己的功能领域:

// ✅ 正确:Store职责单一
export const useUserStore = defineStore('user', {
  state: () => ({
    users: [],
    currentUser: null
  }),
  
  actions: {
    async fetchUsers() {
      // 只处理用户相关逻辑
    },
    
    async updateUser(user: User) {
      // 只处理用户相关逻辑
    }
  }
})

常见问题与解决方案

1. 循环依赖问题

问题:两个或多个Store相互依赖,导致初始化错误。

解决方案

  • 重构Store结构,提取公共逻辑到新的Store
  • 使用运行时延迟获取Store实例
  • 使用事件总线进行通信,解耦Store依赖

2. Store组合导致的性能问题

问题:过多的Store组合导致应用启动缓慢。

解决方案

  • 使用懒加载方式初始化Store
  • 只在需要时才获取依赖的Store
  • 优化Store的初始化逻辑

3. Store状态不一致

问题:多个Store之间的状态不一致,导致应用行为异常。

解决方案

  • 设计清晰的数据流向
  • 使用单一数据源原则
  • 实现状态同步机制
  • 使用事件总线通知状态变化

4. 测试困难

问题:组合后的Store难以测试。

解决方案

  • 为每个Store编写独立的单元测试
  • 使用依赖注入模拟Store依赖
  • 使用测试框架的mock功能

进一步学习资源

  1. Pinia官方文档 - 组合Store
  2. Vue 3官方文档 - 状态管理
  3. 函数式编程 - 组合模式
  4. 设计模式 - 发布-订阅模式
  5. TypeScript官方文档 - 模块化

课后练习

  1. 基础练习

    • 创建两个相互依赖的Store:产品Store和订单Store
    • 在订单Store中调用产品Store的方法
    • 实现产品详情和订单列表功能
    • 在组件中使用组合后的Store
  2. 进阶练习

    • 创建一个事件总线Store
    • 实现用户认证Store和通知Store
    • 使用事件总线实现认证状态变化时的通知
    • 测试事件总线的功能
  3. 组合策略练习

    • 实现水平组合:创建一个组合Store,包含用户、产品和购物车功能
    • 实现垂直组合:创建一个基础Store和多个依赖它的子Store
    • 比较两种组合策略的优缺点
  4. 性能优化练习

    • 创建多个相互依赖的Store
    • 测试不同初始化方式的性能
    • 实现懒加载和延迟获取Store
    • 优化Store组合的性能

通过本节课的学习,你应该能够掌握Pinia中Store间通信的各种方式,理解Store组合的策略和最佳实践,掌握如何避免循环依赖和性能问题,以及如何设计清晰、可维护的Store结构。这些知识将帮助你构建复杂、高效的Vue应用状态管理系统。

« 上一篇 Actions异步操作处理 - Pinia异步逻辑管理 下一篇 » 插件开发与中间件 - Pinia扩展机制