Pinia模块化状态管理的关键在于按业务功能垂直拆分Store并明确职责:每个Store专注单一业务域,State存原始数据、Actions仅变更状态、Getters封装派生逻辑,配合TypeScript接口定义和插件机制保持纯净可测。

Pinia 实现高内聚、可维护的模块化状态管理,关键不在“用不用”,而在于“怎么分”和“怎么管”。核心是让每个 Store 只做一件事,职责清晰、边界明确,且天然支持 TypeScript 类型推导与独立测试。
按业务功能垂直拆分 Store
避免把用户、订单、权限、主题等全塞进一个 useAppStore。应严格按功能域划分,每个 Store 对应一个明确的业务上下文:
- userStore.ts:专注登录态、个人信息、权限列表、会话刷新
- cartStore.ts:只管购物车增删改查、数量校验、总价计算
- themeStore.ts:仅维护当前主题色、字体偏好、暗色模式开关
- notificationStore.ts:统一管理消息队列、自动关闭、错误类型映射
目录结构建议保持扁平,例如:src/stores/user.ts、src/stores/cart.ts,不嵌套子文件夹——Pinia 本身不强制模块嵌套,人为嵌套反而增加路径复杂度。
State、Actions、Getters 各守其责
每个 Store 内部也要划清职责,避免逻辑混杂:
-
state 只存原始数据,不包含派生字段(如
isLogin应由 getter 计算,而非在 state 中手动维护) -
actions 仅负责状态变更,API 调用、表单校验、权限判断等复杂逻辑必须抽离到外部 service(如
authService.login()),再由 action 调用并更新 state -
getters 封装所有派生逻辑,支持缓存、组合使用(如
hasPermission('edit')),禁止在组件中重复写store.permissions.includes(...)
类型定义前置 + 接口驱动开发
TypeScript 不是锦上添花,而是模块化架构的基石。必须为每个 Store 显式声明接口:
interface CartState {
items: CartItem[];
loading: boolean;
error: string | null;
}
export const useCartStore = defineStore('cart', {
state: (): CartState => ({
items: [],
loading: false,
error: null,
}),
getters: {
totalCount: (state) => state.items.reduce((sum, i) => sum + i.quantity, 0),
},
actions: {
async addItem(product: Product) {
try {
this.loading = true;
const item = await cartService.addItem(product);
this.items.push(item);
} finally {
this.loading = false;
}
}
}
});
这样既能获得完整类型提示,也便于后续重构——修改 CartItem 接口时,所有相关代码会立刻报错提醒。
引入持久化与插件机制,但保持 Store 纯净
需要 localStorage 持久化?用 pinia-plugin-persistedstate,但配置写在插件层,不要侵入 Store 本身:
- 在
main.ts或stores/index.ts中统一注册插件 - 只对明确需持久化的 Store 启用(如 user、theme),购物车等临时状态默认不存
- 不把
localStorage.setItem直接写进 action,那会破坏可测试性
同理,日志、错误上报、性能埋点等横切关注点,也应通过 Pinia 插件注入,而非在每个 action 里手写 console.log 或 trackEvent。










