人人都会AI编程

模块化设计与 Store 定义方式

更新时间:2026-07-11

Pinia 的模块化设计理念与 Vuex 有根本性不同。Vuex 通过嵌套的模块树来组织状态,需要借助 namespacedrootGettersdispatch('module/action') 等机制隔离子模块,配置繁琐且类型推断困难。而 Pinia 采用扁平化的多 Store 设计:每个 Store 都是一个独立的“状态单元”,彼此之间可以自由引用,没有强制的层级结构。

多 Store 设计:用文件天然分割模块

在 Pinia 中,模块化是通过创建多个 Store 文件来实现的。每个 Store 文件包含该业务域的 state、getters 和 actions,通过命名约定(如 useUserStoreuseCartStore)和直观的导入(import)来关联。

src/stores/
├── user.js      # 用户相关状态与方法
├── cart.js      # 购物车逻辑
└── product.js   # 商品列表与详情

在任何组件或其它 Store 中,直接引入需要的 Store 实例即可:

// 在 cart.js 中引用 user.js
import { defineStore } from 'pinia'
import { useUserStore } from './user'

export const useCartStore = defineStore('cart', () => {
  const userStore = useUserStore()
  // 现在可以在 cart 的 getter 或 action 中使用 userStore.userInfo
})

这种设计的实际好处很直接:

  • 没有嵌套层级,不需要记忆 dispatch('moduleA/moduleB/action') 的路径,直接 userStore.login()
  • 类型推断完整,TypeScript 下每个 Store 的类型自动推导,不需要额外声明模块类型。
  • 按需引用,不会像 Vuex 的模块树那样一次性注册所有模块,Pinia 的 Store 只有在第一次使用时才会被初始化。

你不需要在项目开始时就决定“全局状态应该划分成几层嵌套”,而是随着业务发展自然地在 stores 目录下新增文件,每个 Store 只关注自己那一块业务。

Store 的两种定义方式

Pinia 提供了两种定义 Store 的语法,选择哪一种完全取决于你的编码习惯。

1. 选项式语法(类似 Vuex)
如果你习惯 Vue 的 Options API,可以用一个对象来定义 Store,包含 stategettersactions 三个固定属性。

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})

注意 state 必须是一个箭头函数,确保每个请求或 SSR 上下文都有独立状态。在 actions 中使用 this 可以访问当前 Store 实例,它会被 Pinia 正确绑定。

2. 组合式语法(推荐)
利用 Composition API 的 refcomputed、普通函数来构建 Store,这就是一个普通的组合式函数(Hook),但通过 defineStore 包装后变成可全局共享的单例。

import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)                        // state
  const doubleCount = computed(() => count.value * 2) // getter
  
  function increment() {                      // action
    count.value++
  }

  return { count, doubleCount, increment }
})

这种写法更贴合 Vue 3 的组合式 API 风格,没有 this,更容易做逻辑抽取和复用。你在 Store 中返回任何响应式数据和方法,它们会在组件中保持响应。

两者对比与选择建议

  • 选项式语法更适合从 Vuex 迁移的项目或喜欢清晰分区的开发者。
  • 组合式语法更灵活,可以自由使用 watch、生命周期钩子(如 onMounted 在 Store 中通常不推荐,但技术上可行),也更容易将业务逻辑抽象成可复用的函数。官方推荐在项目中统一使用组合式语法,因为它与 Vue 3 的组合式 API 理念一致。

真实项目中的 Store 组织

一个典型的用户 Store 可能长这样(组合式语法):

// stores/useUserStore.js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import { loginApi, getUserInfoApi } from '@/api/user'
import router from '@/router'

export const useUserStore = defineStore('user', () => {
  const token = ref(localStorage.getItem('token') || '')
  const userInfo = ref(null)

  const isLoggedIn = computed(() => !!token.value)

  async function login(credentials) {
    const res = await loginApi(credentials)
    token.value = res.token
    localStorage.setItem('token', res.token)
    await fetchUserInfo()
  }

  async function fetchUserInfo() {
    const res = await getUserInfoApi()
    userInfo.value = res
  }

  function logout() {
    token.value = ''
    userInfo.value = null
    localStorage.removeItem('token')
    router.push('/login')
  }

  return { token, userInfo, isLoggedIn, login, fetchUserInfo, logout }
})

组件中使用时简洁直观:

const userStore = useUserStore()
// 调用 userStore.login(form) 或读取 userStore.isLoggedIn

这种模块化方式与组合式定义,让状态管理代码真正变成了“有结构、可追溯的业务层”,而不是一个难以维护的全局大对象。