Article

状态管理 Pinia

更新于:2026-07-11

一、Pinia 简介与核心概念

1.1 什么是 Pinia

概念名称说明注意事项
PiniaVue 的官方推荐状态管理库,用于跨组件共享状态。轻量、类型安全、API 简洁,支持 Vue 2 和 Vue 3。不依赖 Vuex,是 Vuex 5 的设计方向演化而来,现已独立成为首选方案。
状态管理在前端应用中集中管理组件间共享的状态(数据),避免 prop 逐层传递和事件频繁触发。适用于中大型应用,小型项目可直接使用组件状态。

1.2 Pinia 的优势与适用场景

优势/场景说明注意事项
轻量简洁体积小,API 设计更直观,学习成本低。相比 Vuex 更少的模板代码。
类型推导友好原生支持 TypeScript,无需额外配置即可获得完整类型提示。在 TS 项目中开发体验更佳。
模块化设计不需要嵌套模块,每个 Store 天然独立,可按功能组织。避免命名冲突,建议使用唯一 ID。
组合式 API 风格支持 setup() 语法,便于逻辑复用和组织。可与 Composition API 无缝结合。
无 Mutation 概念直接通过 Actions 修改 State,简化流程。所有状态变更仍应通过 Actions 以保证可追踪。
DevTools 集成自动集成 Vue DevTools,支持时间旅行调试。需在开发环境启用。
适用场景用户登录状态、主题配置、购物车、表单数据共享等跨组件状态。避免将所有状态都放入 Store,仅共享必要数据。

1.3 核心概念解析:State、Getters、Actions、Store

概念说明注意事项
Store一个保存状态和业务逻辑的容器,每个 Store 都有唯一的 id。通过 defineStore() 创建。Store 是响应式的,不能被复制或克隆。
State存储共享数据的响应式对象,相当于组件中的 data应为函数返回初始对象,避免引用共享。
Getters类似于组件的 computed,用于派生状态或计算属性。可缓存结果。接收 state 作为参数,不能修改 state。
Actions类似于组件的 methods,用于定义业务逻辑,可包含同步或异步操作。可修改 state,是唯一允许修改 state 的地方(除 $patch 外)。

二、环境搭建与快速上手

2.1 安装 Pinia(Vue 3 项目)

方法语法用途代码示例注意事项
npm 安装npm install pinia安装 Pinia 库npm install pinia确保项目已安装 Vue 3。
yarn 安装yarn add pinia安装 Pinia 库yarn add pinia推荐使用最新稳定版本。
引入并安装import { createApp } from 'vue'
import { createPinia } from 'pinia'
const app = createApp(App)
app.use(createPinia())
在 Vue 应用中注册 Pinia 插件import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
app.use(createPinia())
app.mount('#app')
必须在使用 Store 前调用 app.use(createPinia())

2.2 创建第一个 Store

方法语法用途代码示例注意事项
defineStoredefineStore(id, options)定义一个 Storeimport { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})
id 必须唯一;state 必须是函数;返回一个可调用的函数(Store)。

2.3 在组件中使用 Store

方法语法用途代码示例注意事项
调用 Store 函数useStore()获取 Store 实例import { useCounterStore } from '@/stores/counter'

setup() {
const counter = useCounterStore()
return { counter }
}
必须在 setup()<script setup> 中调用。
访问 Statestore.property读取状态{{ counter.count }}State 是响应式的。
调用 Actionsstore.action()执行业务逻辑counter.increment()可在模板或 JS 中调用。
访问 Gettersstore.getter获取计算属性{{ counter.double }}Getters 是响应式的,自动缓存。

三、State 管理

3.1 定义 State

方法语法用途代码示例注意事项
state 选项state: () => ({ ... })定义 Store 的初始状态export const useUserStore = defineStore('user', {
state: () => ({
name: '',
age: 0,
isLoggedIn: false
})
})
必须返回一个对象;使用函数形式避免多个实例间状态共享。
初始化复杂状态state: () => ({ ... })包含嵌套对象或数组的状态state: () => ({
profile: { name: '', email: '' },
todos: []
})
嵌套属性也是响应式的(基于 Vue 3 的 reactive)。

3.2 访问 State

方法语法用途代码示例注意事项
直接访问store.property读取顶层状态const user = useUserStore()
console.log(user.name)
setup() 或组件实例中均可访问。
访问嵌套状态store.nested.property读取嵌套对象属性console.log(user.profile.name)支持深层访问,响应式自动生效。
在模板中使用{{ store.property }}模板中展示状态{{ user.name }}不需要 return,直接通过实例访问。

3.3 修改 State($patch、$state、Actions)

方法语法用途代码示例注意事项
$patch(对象)store.$patch({ ... })批量更新多个状态字段user.$patch({
name: 'Alice',
age: 25
})
推荐用于同步批量修改;触发一次变更通知。
$patch(函数)store.$patch(state => { ... })基于当前状态进行逻辑更新user.$patch(state => {
state.age += 1
state.isLoggedIn = true
})
适合复杂逻辑;仍为同步操作。
$statestore.$state = { ... }替换整个状态对象user.$state = {
name: 'Bob',
age: 30,
isLoggedIn: true
}
会替换所有状态,慎用;可用于重置状态。
Actions 中修改this.property = value在 Action 内部直接修改 stateactions: {
setName(name) {
this.name = name
}
}
允许异步操作中修改;是推荐的主要修改方式。

四、Getters 使用详解

4.1 定义 Getters

方法语法用途代码示例注意事项
定义 GettergetterName: (state) => { ... }创建计算属性getters: {
fullName: (state) => state.firstName + ' ' + state.lastName
}
接收 state 为参数;自动缓存结果。
使用 thisgetterName() { return this.x + this.y }使用 this 访问其他属性或 gettergetters: {
doubleCount() {
return this.count * 2
}
}
必须使用函数语法(不能箭头函数);this 指向 store。
依赖其他 Getters使用 this 调用其他 getter组合多个 getter 逻辑getters: {
count: () => 10,
doubleCount() { return this.count * 2 },
tripleCount() { return this.doubleCount + this.count }
}
支持跨 getter 调用;响应式更新。

4.2 访问 Getters

方法语法用途代码示例注意事项
直接访问store.getterName读取 getter 值const user = useUserStore()
console.log(user.fullName)
getter 是只读的,不能赋值。
在模板中使用{{ store.getterName }}模板中展示计算结果{{ user.doubleCount }}自动响应 state 变化。
访问方式一致性无需调用函数getter 调用像属性一样// 正确:
user.doubleCount
// 错误:
user.doubleCount()
getter 是属性,不是方法。

4.3 带参数的 Getters

方法语法用途代码示例注意事项
返回函数getterName: () => (param) => { ... }创建可传参的 gettergetters: {
getByIndex: () => (index) => {
return this.todos[index]
}
}
实际返回一个函数,需二次调用。
使用示例store.getter()(arg)调用带参 getterconst todo = user.getByIndex()(0)注意调用方式:getter 返回函数,需再传参。
缓存机制无自动缓存每次调用都会重新计算getters: {
filterByStatus: () => (status) => {
return this.todos.filter(t => t.status === status)
}
}
不像普通 getter 自动缓存,可结合 memoization 优化。

五、Actions 方法

5.1 定义 Actions

方法语法用途代码示例注意事项
定义 ActionactionName() { ... }定义业务逻辑方法actions: {
increment() {
this.count++
}
}
使用函数语法,确保 this 指向 store。
异步 Actionasync actionName() { ... }执行异步操作(如 API 调用)actions: {
async fetchUser(id) {
const res = await api.getUser(id)
this.user = res.data
}
}
使用 async/await 或返回 Promise。
接收参数actionName(param1, param2)传递参数给 Actionactions: {
setName(name) {
this.name = name
}
}
支持任意数量和类型的参数。

5.2 调用 Actions

方法语法用途代码示例注意事项
直接调用store.action()在组件或其他 Action 中调用const user = useUserStore()
user.setName('Alice')
可在 setup()、模板事件、生命周期中调用。
在模板中绑定@click="store.action()"模板中绑定事件调用<button @click="counter.increment">+1</button>需传参时使用箭头函数:
@click="() => store.setAction('val')"
处理返回值store.action().then(...)获取 Promise 返回结果user.fetchUser(1).then(() => {
console.log('加载完成')
})
异步 Action 返回 Promise,可链式调用。

5.3 Actions 中的异步操作

方法语法用途代码示例注意事项
async/awaitasync action() { await fn() }简化异步流程控制actions: {
async loadList() {
this.loading = true
try {
const res = await api.getList()
this.list = res.data
} finally {
this.loading = false
}
}
}
推荐方式,代码更清晰。
Promise 链式调用action() { return fn().then() }传统 Promise 处理actions: {
loadList() {
this.loading = true
return api.getList()
.then(res => { this.list = res.data })
.finally(() => { this.loading = false })
}
}
适用于不支持 async/await 的环境。
错误处理try...catch.catch()捕获异步异常try {
await api.post(data)
} catch (error) {
this.error = error.message
}
建议统一处理错误(如弹窗、日志)。
并行请求Promise.all()同时发起多个请求async fetchAll() {
const [res1, res2] = await Promise.all([
api.getUsers(),
api.getPosts()
])
this.users = res1.data
this.posts = res2.data
}
提升性能,避免串行等待。

六、Store 的高级用法

6.1 Store 之间的相互调用

方法语法用途代码示例注意事项
导入并调用useOtherStore()在一个 Store 中使用另一个 Storeimport { useAuthStore } from './auth'

actions: {
async fetchProfile() {
const auth = useAuthStore()
if (auth.isLoggedIn) {
const res = await api.getProfile(auth.userId)
this.profile = res.data
}
}
}
避免循环依赖(A 调 B,B 又调 A)。
共享状态逻辑跨 Store 访问 state/getters实现功能协作const auth = useAuthStore()
if (auth.isAdmin) {
this.showAdvanced = true
}
建议通过 getters 暴露必要状态。

6.2 模块化 Store 设计

方法语法用途代码示例注意事项
按功能拆分useUserStore, useCartStore将状态按业务模块组织// stores/user.js
export const useUserStore = defineStore(...)

// stores/cart.js
export const useCartStore = defineStore(...)
每个 Store 职责单一,便于维护。
统一入口导出index.js 集中导出简化组件导入// stores/index.js
export * from './user'
export * from './cart'
便于管理,避免路径混乱。
命名规范use + 模块名 + Store统一命名约定useProductStore()
useOrderStore()
提高代码可读性。

6.3 使用 setup Store(组合式 API 风格)

方法语法用途代码示例注意事项
setup() 风格定义defineStore(id, () => { ... })使用组合式 API 定义 Storeexport const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, double, increment }
})
更灵活,可使用 refcomputedwatch 等。
混合使用可与 Options 风格共存逐步迁移或选择合适风格支持在同一项目中同时使用两种风格。建议团队统一风格。
类型推导优势更好的 TS 支持自动推导返回类型const useStore = defineStore('x', () => {
const n = ref(0)
return { n }
})
const store = useStore()
// store.n 类型自动推导为 number
减少类型注解,提升开发体验。

七、插件与扩展

7.1 Pinia 插件机制概述

概念说明注意事项
插件机制Pinia 允许通过插件扩展 Store 的功能,插件是一个函数,接收 context 参数。插件在 app.use(pinia) 后自动应用到所有 Store。
插件上下文插件函数接收一个对象,包含 { pinia, app, store, options } 等属性。可用于访问应用实例、Store 实例和定义配置。
扩展 Store插件可以向 Store 添加新属性、方法或修改现有行为。避免覆盖已有关键属性,防止冲突。
应用时机插件在 Store 创建时执行,可用于初始化逻辑或监听状态变化。适合做日志、持久化、同步等通用功能。

7.2 编写自定义插件

方法语法用途代码示例注意事项
定义插件函数export function myPlugin(context) { ... }创建可复用的扩展逻辑function myPlugin({ store }) {
store.log = () => {
console.log('State of ' + store.id + ':', store.$state)
}
}

pinia.use(myPlugin)
插件函数必须返回要添加的属性或 undefined
扩展 Store 属性return { key: value }向 Store 添加新属性return {
createdAt: new Date(),
version: '1.0.0'
}
可通过 store.$createdAt 访问。
扩展 Store 方法return { method() { ... } }添加新方法return {
reset() {
this.$patch(state => {
Object.keys(state).forEach(k => delete state[k])
})
}
}
方法中 this 指向 Store 实例。
监听状态变化store.$subscribe()响应状态变更store.$subscribe((mutation, state) => {
console.log('State changed:', state)
})
适合做日志记录或持久化。
异步初始化可返回 Promise执行异步设置逻辑return new Promise(resolve => {
setTimeout(() => {
store.$isReady = true
resolve()
}, 1000)
})
支持异步插件初始化。

7.3 常用插件介绍(如持久化存储)

插件名称用途代码示例注意事项
pinia-plugin-persistedstate将 Store 状态持久化到 localStorage 或 sessionStorageimport piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

pinia.use(piniaPluginPersistedstate)

// 在 defineStore 中:
export const useUserStore = defineStore('user', {
state: () => ({ name: '' }),
persist: true // 或配置对象
})
需单独安装;默认使用 localStorage;注意敏感数据安全。
配置持久化范围persist: { key, paths, storage }控制哪些状态被持久化persist: {
key: 'my_user_data',
paths: ['name', 'theme'],
storage: sessionStorage
}
自定义序列化serializer: { serialize, deserialize }自定义存储/读取逻辑persist: {
serializer: {
serialize: JSON.stringify,
deserialize: JSON.parse
}
}

八、TypeScript 支持

8.1 为 Store 添加类型定义

方法语法用途代码示例注意事项
显式接口定义interface StoreState { ... }定义 State 结构类型interface UserState {
name: string
age: number
}

state: (): UserState => ({ name: '', age: 0 })
提高类型安全性,便于团队协作。
定义 Action 类型interface StoreActions { ... }为 Actions 提供类型interface UserActions {
setName(name: string): void
fetchUser(id: number): Promise<void>
}
通常用于 setup 风格 Store。
组合完整类型type UserStore = ReturnType<...>获取完整 Store 类型type UserStore = {
state: UserState
getters: UserGetters
actions: UserActions
}
可用于函数参数或类型断言。

8.2 类型推导与泛型使用

方法语法用途代码示例注意事项
自动类型推导defineStore('id', { ... })Pinia 自动推导 Options 风格 Store 类型const useStore = defineStore('main', {
state: () => ({ count: 0 }),
getters: { double: s => s.count * 2 }
})
// count 推导为 number
大多数情况无需手动注解。
setup 风格泛型defineStore<...>()显式指定返回类型const useSetupStore = defineStore('setup', () => {
const count = ref(0)
return { count }
})
// count 类型自动为 Ref<number>
可结合 ReturnType 获取类型。
泛型 Store 工厂function useGenericStore<T>()创建可复用的泛型 Storefunction useListStore<T>(id: string) {
return defineStore(id, () => {
const list = ref<T[]>([])
return { list }
})()
}
适合创建通用数据容器。

8.3 在 TS 中安全使用 State、Getters、Actions

方法语法用途代码示例注意事项
类型断言 Storeconst store = useStore() as StoreType强制指定 Store 类型const userStore = useUserStore()
userStore.setName('Bob') // 类型安全
确保类型定义正确,避免误用。
可选链访问store.getter?.value安全访问可能未初始化的属性const name = userStore.profile?.name防止运行时错误。
异常处理try-catch 包裹 Actions安全调用可能失败的方法try {
await userStore.fetchUser(1)
} catch (e: unknown) {
if (e instanceof Error) {
console.error(e.message)
}
}
结合 TypeScript 类型守卫提升安全性。
严格模式"strict": true in tsconfig.json启用全面类型检查{
"compilerOptions": {
"strict": true
}
}
推荐开启,提前发现潜在类型问题。

九、开发工具与调试

9.1 配置 Pinia DevTools

方法语法用途代码示例注意事项
自动集成无需额外配置在开发环境下自动启用 DevToolsimport { createPinia } from 'pinia'
const pinia = createPinia()
app.use(pinia)
确保使用开发版本的 Vue 和 Pinia。
手动配置 DevToolsdevtools: { enabled: boolean }显式控制 DevTools 行为const pinia = createPinia()
pinia.useDevtools = true // 或 false
在生产环境应禁用。
指定 Store 名称空间devtools: { storeId }在 DevTools 中自定义显示名称defineStore('user', {
// ...
devtools: {
storeId: 'auth/user'
}
})
便于在复杂应用中定位 Store。
环境判断根据 NODE_ENV 控制仅在开发环境启用调试devtools: {
enabled: import.meta.env.DEV
}
避免生产环境暴露状态。

9.2 调试 State 变化与 Action 调用

方法语法用途代码示例注意事项
查看 State 快照DevTools > State 面板实时查看当前所有 Store 状态无代码,通过浏览器插件查看。支持展开嵌套对象和数组。
监听 Action 记录DevTools > Actions 面板查看每次 Action 调用及其参数每次调用 increment() 都会记录。包括同步和异步 Action。
添加 Action 元数据$patch 或 action 中附加信息提供更详细的调试信息store.$patch({ count: 1 }, {
type: 'manually_set'
})
可在 DevTools 中显示附加信息。
自定义事件标签在 Action 中使用 console.log辅助调试复杂逻辑actions: {
async fetchData() {
console.log('[Store] 开始加载数据...')
// ...
}
}
建议在生产环境移除或使用日志级别控制。

9.3 时间旅行调试(Time Travel)

方法语法用途代码示例注意事项
回退到历史状态DevTools 时间轴拖动将应用状态回滚到之前时刻无代码,通过界面操作。仅限开发环境使用。
重放 Action 序列逐个点击 Action 记录逐步执行历史操作观察变化点击 Actions 列表中的条目。有助于复现用户操作路径。
快照保存与恢复Save State / Commit保存当前状态或恢复到保存点使用 DevTools 的 Commit 按钮。可用于测试特定状态场景。
异步操作支持支持 async/await 记录正确记录异步 Action 的开始和结束async loadList() { await api.get() }在 DevTools 中显示为一个完整操作。

十、最佳实践与常见问题

10.1 状态管理设计原则

原则说明注意事项
单一职责每个 Store 聚焦一个业务领域(如 user、cart)避免创建 giant store,提高可维护性。
状态最小化仅将需要共享的状态放入 Store组件私有状态保留在组件内部。
State 只读不直接修改 state,通过 Actions 修改保证状态变更可追踪,利于调试。
Actions 统一入口所有状态变更逻辑封装在 Actions 中包括同步和异步操作。
Getters 无副作用Getter 应为纯函数,不修改状态避免在 getter 中调用 API 或修改 this
命名清晰使用语义化命名(如 fetchUserssetTheme避免 useXdoY 等模糊命名。

10.2 性能优化建议

方法说明注意事项
合理使用 getters利用缓存机制避免重复计算复杂计算应使用 getter 而非模板表达式。
避免过度响应式不要将大型数据结构全部放入 state可考虑分页加载或虚拟滚动。
按需订阅使用 store.$subscribe 时及时清理在组件销毁时调用返回的 unsubscribe 函数。
持久化选择性字段通过 persist.paths 配置避免存储大量不必要数据到 localStorage。
异步操作防抖对频繁触发的 Action 增加防抖如搜索建议请求。
懒加载 Store在需要时才导入和使用 Store减少初始加载体积。

10.3 常见错误与解决方案

问题现象可能原因解决方案注意事项
Store 状态未响应未正确导入或调用 useStore()确保在 setup() 中调用 const store = useStore()Store 必须被实例化才能响应。
State 修改无效直接替换 state 而未使用 $patch$state使用 store.$patch()store.$state = {}直接赋值非根属性可能有效,但不推荐。
循环依赖 StoreA Store 导入 B,B 又导入 A重构逻辑,提取公共部分或使用事件总线defineStore 外部导入 Store。
TypeScript 类型错误类型未正确推导或断言使用 ReturnType 获取类型检查接口定义是否匹配。
持久化不生效未安装或注册 pinia-plugin-persistedstate安装插件并正确配置 persist 选项检查浏览器存储权限和容量。
DevTools 不显示生产模式或配置错误确保开发环境且未手动禁用 devtools检查浏览器插件是否启用。