《Pinia 从入门到精通》Vue 3 官方状态管理 -- 进阶使用篇
《Pinia 从入门到精通》Vue 3 官方状态管理 – 基础入门篇
《Pinia 从入门到精通》Vue 3 官方状态管理 – 进阶使用篇
《Pinia 从入门到精通》Vue 3 官方状态管理 – 插件扩展篇
目录
- Store 的模块化设计
- 4.1 多模块结构设计
- ✅ 推荐目录结构(中大型项目)
- 4.2 定义独立模块 Store
- 4.3 在组件中组合多个 Store
- 4.4 跨模块调用(解耦调用)
- 4.5 模块化 Store 的命名约定
- 4.6 所有 Store 统一导出(可选)
- ✅ 小结
- 类型系统集成(TypeScript 支持)
- 5.1 Store 的类型推导基础
- 5.2 自定义类型(推荐)
- ✅ 定义接口
- ✅ 使用泛型定义 Store
- 5.3 Setup Store(组合式写法)中的类型支持
- 5.4 类型提示与 IDE 自动补全效果
- 5.5 类型约束下的好处
- ✅ 小结
- 持久化存储与插件机制
- 6.1 什么是 Pinia 插件?
- 6.2 状态持久化插件:`pinia-plugin-persistedstate`
- ✅ 安装
- ✅ 注册插件
- 6.3 使用持久化配置
- 6.4 自定义持久化策略
- 6.5 多模块下持久化组合
- 6.6 自定义插件机制(进阶)
- 6.7 生命周期钩子:`$subscribe` & `$onAction`
- 1. `$subscribe` —— 监听状态变化
- 2. `$onAction` —— 监听 Action 执行
- ✅ 小结
- 最佳实践总结与项目结构规范化设计
- 7.1 推荐项目结构
- 7.2 Store 命名规范
- 7.3 状态设计原则
- 7.4 Store 类型系统统一
- 7.5 持久化策略标准化
- 7.6 自动化导出 Store
- 7.7 组合逻辑封装:`useXXXLogic`
- ✅ 实战应用:多页面应用的 Store 模型设计范式
- ✅ 统一 Store 模板(可直接复制)
- ✅ 结语:Pinia 最佳实践总览图
Store 的模块化设计
随着项目规模扩大,单一 Store 会迅速膨胀,导致维护困难。
我们将从实际项目结构出发,讲解如何构建多模块 Store,支持清晰组织、职责分离和可维护性。
Pinia 提供了天然模块化的设计,每一个 Store 就是一个模块,天生支持按需加载、组合调用。
4.1 多模块结构设计
✅ 推荐目录结构(中大型项目)
src/
├── stores/
│ ├── index.ts # 导出所有 store(可选)
│ ├── user.ts # 用户模块
│ ├── auth.ts # 登录认证模块
│ ├── cart.ts # 购物车模块
│ └── product.ts # 商品模块
每个模块都用 defineStore
定义自己的状态、逻辑、计算属性,保持内聚。
4.2 定义独立模块 Store
// stores/user.ts
import { defineStore } from 'pinia'export const useUserStore = defineStore('user', {state: () => ({name: '',email: ''}),actions: {setUser(name: string, email: string) {this.name = namethis.email = email}}
})
// stores/cart.ts
import { defineStore } from 'pinia'export const useCartStore = defineStore('cart', {state: () => ({items: [] as { id: number; name: string; qty: number }[]}),getters: {totalItems: (state) => state.items.reduce((sum, item) => sum + item.qty, 0)},actions: {addItem(item: { id: number; name: string; qty: number }) {this.items.push(item)}}
})
4.3 在组件中组合多个 Store
<script setup lang="ts">
import { useUserStore } from '@/stores/user'
import { useCartStore } from '@/stores/cart'const userStore = useUserStore()
const cartStore = useCartStore()function checkout() {console.log(`${userStore.name} 正在购买 ${cartStore.totalItems} 件商品`)
}
</script>
4.4 跨模块调用(解耦调用)
Pinia 中不同 Store 可相互独立调用,而无需手动注入依赖:
// stores/order.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'export const useOrderStore = defineStore('order', {actions: {submitOrder() {const userStore = useUserStore()console.log(`下单用户:${userStore.name}`)// ...业务逻辑}}
})
注意:调用其他 Store 必须在 action 中动态获取,而不能在 Store 顶层直接引入(避免依赖环问题)。
4.5 模块化 Store 的命名约定
内容 | 命名规则 | 示例 |
---|---|---|
Store 函数 | useXxxStore | useUserStore |
文件名 | 模块名小写 | user.ts |
Store ID | 与函数名一致的小写形式 | 'user' |
命名空间 | 使用 id 区分,无需嵌套定义 | 所有 Store 自动隔离 |
4.6 所有 Store 统一导出(可选)
你可以在 stores/index.ts
中统一导出,方便使用:
// stores/index.ts
export * from './user'
export * from './cart'
export * from './order'
// 使用
import { useUserStore, useCartStore } from '@/stores'
✅ 小结
模块化是构建大型 Vue 应用的核心策略,Pinia 以其函数式 Store 和天然隔离的 Store ID 设计,使模块化变得:
- 更加清晰(每个模块职责单一)
- 更易维护(按需加载,互不干扰)
- 更易测试(每个 Store 独立可测试)
类型系统集成(TypeScript 支持)
Pinia 天生支持 TypeScript,基于其函数式 API 和明确的声明结构,使得类型推导更加自然、精准。
5.1 Store 的类型推导基础
最基础的 defineStore
形式在 TS 中已经具备类型提示能力:
export const useCounterStore = defineStore('counter', {state: () => ({count: 0,name: 'Counter Module'}),getters: {doubleCount: (state) => state.count * 2},actions: {increment() {this.count++}}
})
此时使用该 Store 时会自动获得类型提示:
const store = useCounterStore()
store.count // number ✅
store.increment() // 自动补全 ✅
5.2 自定义类型(推荐)
虽然大多数场景下可自动推导,但我们仍推荐使用接口显式定义 state
、getters
、actions
类型以获得更强可维护性。
✅ 定义接口
// types/store.d.ts
export interface CounterState {count: numbername: string
}export interface CounterGetters {doubleCount(state: CounterState): number
}export interface CounterActions {increment(): void
}
✅ 使用泛型定义 Store
import { defineStore, StoreDefinition } from 'pinia'
import type { CounterState, CounterActions, CounterGetters } from '@/types/store'export const useCounterStore: StoreDefinition<'counter', CounterState, CounterGetters, CounterActions> = defineStore('counter', {state: (): CounterState => ({count: 0,name: 'Counter Store'}),getters: {doubleCount: (state) => state.count * 2},actions: {increment() {this.count++}}
})
5.3 Setup Store(组合式写法)中的类型支持
对于复杂逻辑,我们可以使用组合式写法,即 setup
形式的 Store,TS 类型控制更灵活。
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'export const useUserStore = defineStore('user', () => {const name = ref('Alice')const age = ref(25)const summary = computed(() => `${name.value}(${age.value}岁)`)function setName(newName: string) {name.value = newName}return {name,age,summary,setName}
})
此种写法中,Pinia 能自动推导返回值类型,无需额外泛型,只要你在返回时显式写出 ref
/ computed
。
5.4 类型提示与 IDE 自动补全效果
- State 属性:可识别为
ref<number>
/ref<string>
等 - Getter 属性:识别为
ComputedRef
- Action 方法:自动推导参数和返回值类型
举个例子:
const userStore = useUserStore()
userStore.age.value // number ✅
userStore.summary.value // string ✅
userStore.setName('Bob') // ✅
5.5 类型约束下的好处
特性 | 说明 |
---|---|
自动补全 | 所有属性、方法可自动提示,无需手动查找 |
静态校验 | 错误属性/参数在编译期即可发现,避免运行时异常 |
支持重构 | 改名、移动属性时 IDE 可自动跟踪更新引用 |
接口复用 | 同一份接口可复用在组件、接口、后端通讯中 |
✅ 小结
Pinia 在 TypeScript 项目中具备一流的类型体验:
- 推荐用接口明确 State / Action / Getter 结构
- 可选使用组合式写法提升灵活性与组合能力
- 强类型推导贯穿编写、使用、调试各个环节
持久化存储与插件机制
本节将深入讲解如何使用 Pinia 插件 实现 Store 状态的持久化存储,以及自定义扩展 Store 功能。
在实际项目中,我们常常需要:
- 保持用户登录信息,即使刷新页面也不丢失
- 记住用户的主题偏好、语言设置
- 在多个 Tab 页面之间共享状态
Pinia 原生支持插件机制,非常适合用于这些扩展场景。
6.1 什么是 Pinia 插件?
插件是一个函数,在每个 Store 实例创建时执行。你可以通过插件:
- 添加全局状态
- 劫持/监听 Store 生命周期
- 自动同步状态到 localStorage / sessionStorage
- 注入外部依赖(如 Axios)
6.2 状态持久化插件:pinia-plugin-persistedstate
最常用的第三方插件是 pinia-plugin-persistedstate
,可自动将状态持久化到本地存储。
✅ 安装
npm install pinia-plugin-persistedstate
✅ 注册插件
// main.ts
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)app.use(pinia)
6.3 使用持久化配置
只需在 defineStore
中增加 persist
配置:
// stores/user.ts
export const useUserStore = defineStore('user', {state: () => ({token: '',theme: 'light'}),persist: true // 默认使用 localStorage
})
刷新后依然保留 token 和主题偏好。
6.4 自定义持久化策略
persist: {enabled: true,strategies: [{key: 'user-token',storage: sessionStorage,paths: ['token'] // 只存 token}]
}
key
:本地存储的键名storage
:可选localStorage
或sessionStorage
paths
:只持久化指定字段
6.5 多模块下持久化组合
每个模块 Store 都可独立配置 persist
,互不影响:
// user.ts => 存 token 到 sessionStorage
persist: {storage: sessionStorage,paths: ['token']
}// settings.ts => 存 theme 到 localStorage
persist: {storage: localStorage,paths: ['theme']
}
6.6 自定义插件机制(进阶)
你可以自定义自己的插件来扩展 Store 功能:
// plugins/logger.ts
import type { PiniaPluginContext } from 'pinia'export function loggerPlugin({ store }: PiniaPluginContext) {store.$subscribe((mutation, state) => {console.log(`[${mutation.storeId}]`, mutation.type, mutation.events)})
}
注册插件:
pinia.use(loggerPlugin)
这样每次状态改变都会打印日志。
6.7 生命周期钩子:$subscribe
& $onAction
Pinia 提供两种原生生命周期钩子:
1. $subscribe
—— 监听状态变化
const userStore = useUserStore()userStore.$subscribe((mutation, state) => {console.log('状态发生变化:', mutation, state)
})
2. $onAction
—— 监听 Action 执行
userStore.$onAction(({ name, args, after, onError }) => {console.log(`Action 被调用: ${name}`, args)after(() => console.log(`${name} 执行成功`))onError((err) => console.error(`${name} 报错`, err))
})
非常适合做埋点、错误日志等逻辑。
✅ 小结
Pinia 插件机制极为强大和灵活:
功能类型 | 工具 | 用途描述 |
---|---|---|
本地持久化 | pinia-plugin-persistedstate | 自动同步状态到 Storage |
状态监听 | $subscribe | 跟踪 State 的变化 |
行为监听 | $onAction | 跟踪 Action 的执行情况 |
自定义扩展 | 插件函数 | 注入工具、处理副作用、封装逻辑等 |
最佳实践总结与项目结构规范化设计
将基于前面内容,系统梳理一套企业级 Pinia 状态管理的最佳实践,从模块设计、命名规范、状态解耦、持久化、类型安全等多个维度,构建一个清晰、稳定、可维护、易扩展的 Store 架构体系。
7.1 推荐项目结构
src/
├── stores/ # 所有 Pinia Store 模块
│ ├── user.ts # 用户相关状态
│ ├── auth.ts # 权限与登录认证
│ ├── ui.ts # UI 状态(如 sidebar)
│ ├── settings.ts # 全局设置项
│ └── index.ts # 自动导出所有 Store
├── types/ # Store 类型定义
│ └── store.d.ts
├── plugins/ # 自定义插件(如 router 注入、日志等)
├── composables/ # 组合逻辑封装,可配合 Store 使用
7.2 Store 命名规范
类型 | 命名建议 | 示例 |
---|---|---|
Store 名称 | useXxxStore | useUserStore |
ID(storeId) | 模块名小写 | user , auth , ui |
文件名 | 模块名小写 | user.ts , auth.ts |
命名统一,利于团队协作和自动化生成。
7.3 状态设计原则
- 一个模块职责单一,避免巨型 Store
- 状态最小化:只存 UI 需要的状态,不要存派生数据(放 getters)
- 与组件无关的数据放 Store,临时数据放组件
- 使用
ref
,computed
,watch
配合使用 Store 提高响应性控制
7.4 Store 类型系统统一
统一定义 Store 的 state
, getters
, actions
类型接口:
// types/store.d.ts
export interface UserState { name: string; age: number }
export interface UserActions { login(): void; logout(): void }
export interface UserGetters { isAdult: boolean }
结合 StoreDefinition
:
export const useUserStore: StoreDefinition<'user', UserState, UserGetters, UserActions> =defineStore('user', {state: (): UserState => ({ ... }),...})
优点:
- 强类型保障
- 便于重构
- 编辑器智能提示清晰
7.5 持久化策略标准化
- token、用户信息 → 存到
sessionStorage
(浏览器关闭清空) - 用户偏好、主题设置 → 存到
localStorage
(长期保存) - 配置统一封装成策略常量:
const localPersist = {storage: localStorage,paths: ['theme', 'language']
}
7.6 自动化导出 Store
// stores/index.ts
export * from './user'
export * from './auth'
export * from './settings'
支持模块自动导入:
import { useUserStore, useAuthStore } from '@/stores'
7.7 组合逻辑封装:useXXXLogic
业务逻辑不要堆在组件里,应该封装成组合逻辑:
// composables/useLogin.ts
export function useLogin() {const userStore = useUserStore()const doLogin = async (formData) => {await userStore.login(formData)router.push('/dashboard')}return { doLogin }
}
- 提高复用性
- 分离 UI 与业务逻辑
- 组件更轻盈可测试
✅ 实战应用:多页面应用的 Store 模型设计范式
模块名称 | 典型状态字段 | 持久化 | 常驻内存 | 是否解耦 UI |
---|---|---|---|---|
user | token , info | ✅ | ✅ | ✅ |
ui | sidebar , theme | ✅ | ✅ | ✅ |
auth | roles , routes | ❌ | ✅ | ✅ |
search | keyword , filters | ❌ | ❌ | ❌(页面级) |
✅ 统一 Store 模板(可直接复制)
// stores/xxx.ts
import { defineStore } from 'pinia'
import type { XxxState } from '@/types/store'export const useXxxStore = defineStore('xxx', {state: (): XxxState => ({ ... }),getters: {...},actions: {...},persist: {enabled: true,strategies: [...]}
})
✅ 结语:Pinia 最佳实践总览图
+-------------------------------+
| 模块划分清晰 |
| ↓ |
| 单一职责 |
| ↓ |
| 类型定义接口统一 |
| ↓ |
| Plugin 封装 & 逻辑抽离 |
| ↓ |
| 状态最小化 & 可持久化 |
| ↓ |
| 与 Router / API / UI 解耦 |
+-------------------------------+
Pinia 的设计理念是简单、透明、类型友好。配合组合式 API,它不仅可以替代 Vuex,还能帮助我们构建更现代、更可控、更高效的前端架构。