Store 组合 #

概述 #

在实际应用中,我们经常需要在多个 Store 之间共享状态或调用彼此的方法。Pinia 提供了多种方式来实现 Store 之间的组合和协作。

访问其他 Store #

在 Getters 中访问 #

ts
// stores/user.ts
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    id: 1,
    name: 'John',
    email: 'john@example.com'
  })
})
ts
// stores/cart.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: [] as CartItem[]
  }),
  getters: {
    summary(): string {
      const userStore = useUserStore()
      return `${userStore.name} 的购物车有 ${this.items.length} 件商品`
    },
    
    // 返回用户专属的购物车标识
    cartId(): string {
      const userStore = useUserStore()
      return `cart-${userStore.id}`
    }
  }
})

在 Actions 中访问 #

ts
// stores/order.ts
import { defineStore } from 'pinia'
import { useCartStore } from './cart'
import { useUserStore } from './user'

export const useOrderStore = defineStore('order', {
  state: () => ({
    orders: [] as Order[]
  }),
  actions: {
    async createOrder() {
      const cartStore = useCartStore()
      const userStore = useUserStore()
      
      // 验证用户是否登录
      if (!userStore.id) {
        throw new Error('请先登录')
      }
      
      // 验证购物车是否为空
      if (cartStore.items.length === 0) {
        throw new Error('购物车为空')
      }
      
      const order = {
        userId: userStore.id,
        items: [...cartStore.items],
        total: cartStore.total,
        createdAt: new Date()
      }
      
      const response = await api.createOrder(order)
      this.orders.push(response)
      
      // 清空购物车
      cartStore.clearCart()
      
      return response
    }
  }
})

Setup Store 中访问 #

ts
// stores/dashboard.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useUserStore } from './user'
import { useCartStore } from './cart'
import { useOrderStore } from './order'

export const useDashboardStore = defineStore('dashboard', () => {
  const userStore = useUserStore()
  const cartStore = useCartStore()
  const orderStore = useOrderStore()
  
  const loading = ref(false)
  
  // 组合多个 store 的数据
  const summary = computed(() => ({
    userName: userStore.name,
    cartItems: cartStore.itemCount,
    orderCount: orderStore.orders.length
  }))
  
  // 组合多个 store 的方法
  async function loadAll() {
    loading.value = true
    try {
      await Promise.all([
        userStore.fetchUser(),
        cartStore.fetchCart(),
        orderStore.fetchOrders()
      ])
    } finally {
      loading.value = false
    }
  }
  
  return {
    loading,
    summary,
    loadAll
  }
})

共享状态 #

使用外部状态 #

多个 Store 可以共享同一个外部状态:

ts
// stores/shared.ts
import { ref } from 'vue'

// 共享的主题状态
export const sharedTheme = ref<'light' | 'dark'>('light')
export const sharedLocale = ref('zh-CN')
ts
// stores/user.ts
import { defineStore } from 'pinia'
import { sharedTheme, sharedLocale } from './shared'

export const useUserStore = defineStore('user', () => {
  const theme = sharedTheme
  const locale = sharedLocale
  
  function setTheme(newTheme: 'light' | 'dark') {
    theme.value = newTheme
  }
  
  return { theme, locale, setTheme }
})
ts
// stores/admin.ts
import { defineStore } from 'pinia'
import { sharedTheme, sharedLocale } from './shared'

export const useAdminStore = defineStore('admin', () => {
  const theme = sharedTheme
  const locale = sharedLocale
  
  return { theme, locale }
})

使用 provide/inject #

在组件树中共享状态:

ts
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)

// 提供全局状态
app.provide('globalConfig', {
  apiBaseUrl: 'https://api.example.com',
  version: '1.0.0'
})
ts
// stores/config.ts
import { defineStore } from 'pinia'
import { inject } from 'vue'

export const useConfigStore = defineStore('config', () => {
  const config = inject('globalConfig') as {
    apiBaseUrl: string
    version: string
  }
  
  return { config }
})

Store 组合模式 #

1. 扇出模式(Fan-out) #

一个 Store 调用多个其他 Store:

ts
// stores/app.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'
import { useCartStore } from './cart'
import { useNotificationStore } from './notification'

export const useAppStore = defineStore('app', () => {
  const userStore = useUserStore()
  const cartStore = useCartStore()
  const notificationStore = useNotificationStore()
  
  async function initialize() {
    await userStore.fetchUser()
    await cartStore.fetchCart()
    notificationStore.show('应用初始化完成')
  }
  
  async function logout() {
    await userStore.logout()
    cartStore.clearCart()
    notificationStore.show('已退出登录')
  }
  
  return { initialize, logout }
})

2. 扇入模式(Fan-in) #

多个 Store 调用同一个 Store:

ts
// stores/auth.ts
import { defineStore } from 'pinia'

export const useAuthStore = defineStore('auth', {
  state: () => ({
    token: null as string | null
  }),
  getters: {
    isAuthenticated: (state) => !!state.token
  }
})
ts
// stores/user.ts
import { defineStore } from 'pinia'
import { useAuthStore } from './auth'

export const useUserStore = defineStore('user', {
  actions: {
    async fetchUser() {
      const authStore = useAuthStore()
      if (!authStore.isAuthenticated) {
        throw new Error('未授权')
      }
      // ...
    }
  }
})
ts
// stores/order.ts
import { defineStore } from 'pinia'
import { useAuthStore } from './auth'

export const useOrderStore = defineStore('order', {
  actions: {
    async fetchOrders() {
      const authStore = useAuthStore()
      if (!authStore.isAuthenticated) {
        throw new Error('未授权')
      }
      // ...
    }
  }
})

3. 链式模式 #

Store 之间形成调用链:

ts
// stores/product.ts
export const useProductStore = defineStore('product', {
  state: () => ({ products: [] }),
  actions: {
    async fetchProducts() {
      // ...
    }
  }
})
ts
// stores/cart.ts
import { useProductStore } from './product'

export const useCartStore = defineStore('cart', {
  actions: {
    async addToCart(productId: number) {
      const productStore = useProductStore()
      const product = productStore.products.find(p => p.id === productId)
      // ...
    }
  }
})
ts
// stores/order.ts
import { useCartStore } from './cart'

export const useOrderStore = defineStore('order', {
  actions: {
    async createOrder() {
      const cartStore = useCartStore()
      // 使用购物车数据创建订单
      // ...
    }
  }
})

避免循环依赖 #

问题场景 #

ts
// stores/a.ts
import { useBStore } from './b'

export const useAStore = defineStore('a', {
  actions: {
    doSomething() {
      const bStore = useBStore()  // 依赖 B
    }
  }
})

// stores/b.ts
import { useAStore } from './a'

export const useBStore = defineStore('b', {
  actions: {
    doSomethingElse() {
      const aStore = useAStore()  // 依赖 A - 循环依赖!
    }
  }
})

解决方案 #

1. 提取共享逻辑 #

ts
// stores/shared.ts
export function sharedLogic() {
  // 共享的业务逻辑
}
ts
// stores/a.ts
import { sharedLogic } from './shared'

export const useAStore = defineStore('a', {
  actions: {
    doSomething() {
      sharedLogic()
    }
  }
})

2. 使用延迟引用 #

ts
// stores/a.ts
export const useAStore = defineStore('a', {
  actions: {
    doSomething() {
      // 在函数内部引用,而不是模块顶层
      const { useBStore } = require('./b')
      const bStore = useBStore()
    }
  }
})

3. 重构 Store 结构 #

ts
// stores/common.ts
export const useCommonStore = defineStore('common', {
  state: () => ({
    sharedData: null
  })
})
ts
// stores/a.ts
import { useCommonStore } from './common'

export const useAStore = defineStore('a', {
  actions: {
    doSomething() {
      const commonStore = useCommonStore()
    }
  }
})
ts
// stores/b.ts
import { useCommonStore } from './common'

export const useBStore = defineStore('b', {
  actions: {
    doSomethingElse() {
      const commonStore = useCommonStore()
    }
  }
})

实际示例:电商应用 #

ts
// stores/user.ts
import { defineStore } from 'pinia'

interface User {
  id: number
  name: string
  email: string
}

export const useUserStore = defineStore('user', {
  state: () => ({
    user: null as User | null,
    token: null as string | null
  }),
  getters: {
    isAuthenticated: (state) => !!state.token,
    displayName: (state) => state.user?.name || 'Guest'
  },
  actions: {
    async login(email: string, password: string) {
      const response = await api.login({ email, password })
      this.user = response.user
      this.token = response.token
    },
    logout() {
      this.user = null
      this.token = null
    }
  }
})
ts
// stores/product.ts
import { defineStore } from 'pinia'

interface Product {
  id: number
  name: string
  price: number
  stock: number
}

export const useProductStore = defineStore('product', {
  state: () => ({
    products: [] as Product[],
    loading: false
  }),
  actions: {
    async fetchProducts() {
      this.loading = true
      try {
        this.products = await api.getProducts()
      } finally {
        this.loading = false
      }
    }
  }
})
ts
// stores/cart.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'
import { useProductStore } from './product'

interface CartItem {
  productId: number
  quantity: number
}

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: [] as CartItem[]
  }),
  getters: {
    total(): number {
      const productStore = useProductStore()
      return this.items.reduce((sum, item) => {
        const product = productStore.products.find(p => p.id === item.productId)
        return sum + (product?.price || 0) * item.quantity
      }, 0)
    }
  },
  actions: {
    addItem(productId: number, quantity: number = 1) {
      const productStore = useProductStore()
      const product = productStore.products.find(p => p.id === productId)
      
      if (!product) throw new Error('商品不存在')
      if (product.stock < quantity) throw new Error('库存不足')
      
      const existingItem = this.items.find(item => item.productId === productId)
      if (existingItem) {
        existingItem.quantity += quantity
      } else {
        this.items.push({ productId, quantity })
      }
    },
    clearCart() {
      this.items = []
    }
  }
})
ts
// stores/order.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'
import { useCartStore } from './cart'

interface Order {
  id: number
  userId: number
  items: any[]
  total: number
  status: string
}

export const useOrderStore = defineStore('order', {
  state: () => ({
    orders: [] as Order[]
  }),
  actions: {
    async createOrder() {
      const userStore = useUserStore()
      const cartStore = useCartStore()
      
      if (!userStore.isAuthenticated) {
        throw new Error('请先登录')
      }
      
      if (cartStore.items.length === 0) {
        throw new Error('购物车为空')
      }
      
      const order = await api.createOrder({
        userId: userStore.user!.id,
        items: cartStore.items,
        total: cartStore.total
      })
      
      this.orders.push(order)
      cartStore.clearCart()
      
      return order
    },
    async fetchOrders() {
      const userStore = useUserStore()
      if (!userStore.isAuthenticated) return
      
      this.orders = await api.getOrders(userStore.user!.id)
    }
  }
})

最佳实践 #

1. 单向依赖 #

尽量保持单向依赖关系:

text
User Store ← Cart Store ← Order Store

2. 在 Actions 中引用 #

避免在 Store 定义顶层引用其他 Store:

ts
// 推荐
export const useCartStore = defineStore('cart', {
  actions: {
    checkout() {
      const userStore = useUserStore()  // 在 action 内引用
    }
  }
})

// 不推荐
const userStore = useUserStore()  // 顶层引用可能导致问题
export const useCartStore = defineStore('cart', { /* ... */ })

3. 使用 TypeScript 类型 #

ts
// types/stores.ts
export interface UserStore {
  user: User | null
  token: string | null
  login: (email: string, password: string) => Promise<void>
  logout: () => void
}

下一步 #

现在你已经掌握了 Store 组合的使用,接下来让我们学习 Pinia 的高级特性。

最后更新:2026-03-28