名称: pinia-v3 描述: Pinia v3 Vue状态管理,包含defineStore、getters、actions。用于Vue 3存储、Nuxt SSR、Vuex迁移,或遇到存储组合、水合、测试错误。
关键词: pinia, vue状态管理, pinia存储, defineStore, vue 3状态, 状态管理, getters, actions, pinia插件, pinia ssr, nuxt pinia, vuex迁移, 存储组合, pinia测试, 设置存储, 选项存储, storeToRefs, mapState, mapActions, 状态水合, pinia nuxt模块, createPinia, useStore, pinia开发工具, pinia hmr, 热模块替换 许可证: MIT
Pinia v3 - Vue状态管理
状态: 生产就绪 ✅ 最后更新: 2025-11-11 依赖项: Vue 3 (或 Vue 2.7 带 @vue/composition-api) 最新版本: pinia@^3.0.4, @pinia/nuxt@^0.11.2, @pinia/testing@^1.0.2
快速开始 (5分钟)
1. 安装 Pinia
bun add pinia
# 或
bun add pinia
# 或
bun add pinia
对于 Vue <2.7 用户: 同时安装 @vue/composition-api,使用 bun add @vue/composition-api
为什么重要:
- Pinia是官方Vue状态管理库
- 提供比Vuex更好的TypeScript支持
- 消除突变和命名空间复杂性
- 完全开发工具支持,带时间旅行调试
2. 创建和注册Pinia实例
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
关键:
- 在使用任何存储之前安装Pinia
- 在挂载应用之前调用
app.use(pinia) - 每个应用只有一个Pinia实例(除非SSR)
3. 定义你的第一个存储
// stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: 'Eduardo'
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})
4. 在组件中使用存储
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
</script>
<template>
<div>
<p>计数: {{ counter.count }}</p>
<p>双倍: {{ counter.doubleCount }}</p>
<button @click="counter.increment">增加</button>
</div>
</template>
两种存储语法
加载 references/store-syntax-guide.md 获取选项与设置存储的完整比较。
快速概述
Pinia支持两种存储定义语法:
选项存储:
- 类似Vue Options API
- 内置
$reset()方法 - 最佳用于: 简单用例,熟悉Vuex的团队
设置存储:
- 使用Composition API模式
- 完全可组合集成
- 最佳用于: 高级模式,需要观察者/VueUse集成
→ 加载 references/store-syntax-guide.md 获取: 完整语法比较、示例、选择标准
状态、Getters和Actions
加载 references/state-getters-actions.md 获取完整API参考。
快速参考
状态:
- 在
state: () => ({...})(选项) 或ref()(设置) 中定义 - 直接访问:
store.count - 直接突变:
store.count++或store.$patch({...}) - 重置:
store.$reset()(仅选项存储)
Getters:
- 计算属性:
getters: { double: (state) => state.count * 2 } - 使用
this访问其他getters (必须键入返回值)
Actions:
- 业务逻辑:
actions: { increment() { this.count++ } } - 可以是异步
- 直接访问其他存储
存储解构:
import { storeToRefs } from 'pinia'
// ✅ 用于响应性
const { name, count } = storeToRefs(store)
// ✅ Actions可以直接解构
const { increment } = store
→ 加载 references/state-getters-actions.md 获取: 完整API、订阅、存储组合模式、Options API用法
插件和可组合
加载 references/plugins-composables.md 获取完整插件和可组合指南。
插件基础
pinia.use(({ store, options }) => {
// 添加属性到每个存储
return { customProperty: 'value' }
})
可组合集成
选项存储: 限于 useLocalStorage 风格的 state()
设置存储: 完全VueUse/可组合支持
→ 加载 references/plugins-composables.md 获取: 完整插件模式、VueUse集成、TypeScript键入、常见模式(持久化、路由、日志)
在组件外部使用存储
问题
存储需要Pinia实例,这在组件中自动注入,但在模块作用域中不可用。
❌ 错误: 在模块级别访问存储
// router.ts
import { useUserStore } from '@/stores/user'
// ❌ 失败: Pinia尚未安装
const userStore = useUserStore()
router.beforeEach((to) => {
if (userStore.isLoggedIn) { /* ... */ }
})
✅ 正确: 在回调内部访问存储
// router.ts
import { useUserStore } from '@/stores/user'
router.beforeEach((to) => {
// ✅ 工作: 在Pinia安装后调用
const userStore = useUserStore()
if (userStore.isLoggedIn) { /* ... */ }
})
为什么工作: 路由守卫在 app.use(pinia) 完成后执行。
SSR: 显式Pinia实例
// 服务器端
export function setupRouter(pinia) {
router.beforeEach((to) => {
const userStore = useUserStore(pinia) // 显式传递
})
}
服务器端渲染 & Nuxt
加载 references/ssr-and-nuxt.md 获取完整SSR和Nuxt集成指南。
SSR快速参考
状态水合:
- 服务器: 用
devalue()序列化 (非JSON.stringify) - 客户端: 在调用
useStore()之前水合 - 关键: 在actions中所有
useStore()在await之前调用
Nuxt 3/4集成
bunx nuxi@latest module add pinia
自动导入: defineStore, storeToRefs, usePinia, acceptHMRUpdate, 所有存储
→ 加载 references/ssr-and-nuxt.md 获取: 完整SSR模式、Nuxt配置、服务器端数据获取、SSR陷阱、调试
测试
加载 references/testing-guide.md 获取完整测试指南。
测试快速开始
import { setActivePinia, createPinia } from 'pinia'
beforeEach(() => {
setActivePinia(createPinia()) // 每个测试的新鲜Pinia
})
组件测试
bun add -d @pinia/testing
import { createTestingPinia } from '@pinia/testing'
mount(Component, {
global: { plugins: [createTestingPinia()] }
})
→ 加载 references/testing-guide.md 获取: 完整测试模式、存根actions、模拟getters、异步测试、SSR测试
热模块替换 (HMR)
Vite设置
// stores/counter.ts
import { defineStore, acceptHMRUpdate } from 'pinia'
export const useCounterStore = defineStore('counter', {
// 存储定义
})
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useCounterStore, import.meta.hot))
}
Webpack设置
if (import.meta.webpackHot) {
import.meta.webpackHot.accept(acceptHMRUpdate(useCounterStore, import.meta.webpackHot))
}
好处:
- 无需完整页面重载编辑存储
- 在开发期间保留应用状态
- 更快的开发迭代
Options API用法
对于仍使用Options API的项目,加载完整映射器文档。
→ 加载 references/state-getters-actions.md 获取: 完整Options API集成、所有映射器 (mapStores, mapState, mapWritableState, mapActions)
从Vuex迁移
加载 references/vuex-migration.md 获取完整迁移指南。
快速转换概述
关键变化:
- 移除
namespaced(通过存储ID自动) - 消除
mutations(直接状态突变) - 替换
commit()为直接突变 - 替换
rootState/rootGetters为存储导入 - 使用
store.$reset()代替自定义清除mutations
目录: store/modules/ → stores/ (每个模块 = 单独存储)
→ 加载 references/vuex-migration.md 获取: 完整转换步骤、组件迁移、清单、逐步迁移策略
关键规则
始终做
✅ 在 state() 中定义所有状态属性或在设置存储中返回它们
✅ 在组件解构状态/getters时使用 storeToRefs()
✅ 在挂载应用之前调用 app.use(pinia)
✅ 从设置存储返回所有状态 (私有状态破坏SSR/DevTools)
✅ 在组件外部使用时在函数/回调内部调用 useStore()
✅ 使用 acceptHMRUpdate() 支持开发HMR
✅ 当getters使用 this 访问其他getters时键入返回值
✅ 使用 devalue 用于SSR状态序列化 (防止XSS)
✅ 在客户端SSR中调用任何 useStore() 之前水合状态
✅ 在异步actions中所有 useStore() 在 await 之前调用
永远不做
❌ 在存储创建后动态添加状态属性
❌ 直接解构存储而无 storeToRefs() (失去响应性)
❌ 对actions使用箭头函数 (需要 this 上下文)
❌ 在设置存储中返回私有状态 (破坏SSR/DevTools/插件)
❌ 在模块顶级调用 useStore() (在Pinia安装之前)
❌ 创建存储间循环依赖 (双方读取对方状态)
❌ 使用 JSON.stringify() 用于SSR序列化 (易受XSS攻击)
❌ 在actions中 await 后调用 useStore() (破坏SSR)
❌ 忘记当使用 this 时键入getter返回值
❌ 在单元测试中跳过 beforeEach(() => setActivePinia(createPinia()))
已知问题预防
此技能预防 12 个记录的问题:
问题 #1: 直接解构失去响应性
错误: 状态变化在解构后在模板中不更新
为什么发生: JavaScript解构破坏Vue响应性
预防: 始终使用 storeToRefs() 用于状态/getters
问题 #2: 无法动态添加状态属性
错误: 存储创建后添加的新属性不响应
为什么发生: Pinia需要所有属性预先定义以响应性
预防: 在 state() 中声明所有属性,即使初始为 undefined
问题 #3: 在Pinia安装前找不到存储
错误: getActivePinia() 返回未定义
为什么发生: 在 app.use(pinia) 之前调用 useStore()
预防: 在挂载或访问存储之前调用 app.use(pinia)
问题 #4: 设置存储私有状态破坏SSR
错误: 状态在SSR中不正确序列化/水合 为什么发生: 属性不从设置中返回未被跟踪 预防: 从设置存储返回所有状态属性
问题 #5: Getters带 this 不推断类型
错误: TypeScript无法推断当getter使用 this 时的返回类型
来源: Pinia的已知TypeScript限制
预防: 显式键入返回值: getterName(): ReturnType { ... }
问题 #6: Options API存储后缀混淆
错误: 在组件中找不到 this.counterStore
为什么发生: mapStores() 自动添加’Store’后缀
预防: 使用存储名 + ‘Store’ 或调用 setMapStoreSuffix()
问题 #7: Actions在 await 后调用破坏SSR
错误: SSR中使用错误的Pinia实例,导致状态污染
为什么发生: await 在异步函数中改变执行上下文
预防: 在任何 await 语句之前调用所有 useStore()
问题 #8: 循环存储依赖崩溃应用
错误: 最大调用堆栈超出 为什么发生: 双方在初始化期间读取对方状态 预防: 用于跨存储访问使用getters/actions,而非设置时间读取
问题 #9: SSR状态序列化中的XSS漏洞
错误: 状态中的用户输入可执行恶意脚本
为什么发生: JSON.stringify() 不转义可执行代码
预防: 使用 devalue 库用于安全序列化
问题 #10: HMR在开发中不工作
错误: 存储变化需要完整页面重载
为什么发生: Vite/webpack HMR未配置用于存储
预防: 添加 acceptHMRUpdate() 块到每个存储文件
问题 #11: 可组合返回函数破坏选项存储
错误: 存储状态包含不可序列化函数
为什么发生: 选项存储 state() 只能返回可写refs
预防: 使用设置存储用于复杂可组合,或仅提取可写状态
问题 #12: 状态在单元测试之间未重置
错误: 测试相互影响,偶尔失败
为什么发生: 单个Pinia实例在测试间共享
预防: 在测试套件中 beforeEach(() => setActivePinia(createPinia()))
包版本 (验证 2025-11-21)
核心: pinia@^3.0.4, vue@^3.5.24
Nuxt: @pinia/nuxt@^0.11.2, nuxt@^3.13.0
测试: @pinia/testing@^1.0.2, vitest@^1.0.0
SSR: devalue@^5.3.2 (用于安全序列化)
常见模式
见参考文件获取完整模式示例:
- 认证存储 →
references/state-getters-actions.md - 持久化插件 →
references/plugins-composables.md - 表单存储 →
references/store-syntax-guide.md(设置存储示例) - 路由集成 →
references/state-getters-actions.md(在组件外部访问存储)
官方文档
- Pinia: https://pinia.vuejs.org/
- 开始使用: https://pinia.vuejs.org/getting-started.html
- 核心概念: https://pinia.vuejs.org/core-concepts/
- SSR指南: https://pinia.vuejs.org/ssr/
- Nuxt集成: https://pinia.vuejs.org/ssr/nuxt.html
- 测试: https://pinia.vuejs.org/cookbook/testing.html
- Vuex迁移: https://pinia.vuejs.org/cookbook/migration-vuex.html
- GitHub: https://github.com/vuejs/pinia
故障排除
问题: “getActivePinia() was called with no active Pinia”
解决方案:
- 确保在挂载之前调用
app.use(pinia) - 如果在组件外部,在回调/函数内部调用
useStore() - 对于SSR,显式传递pinia实例:
useStore(pinia)
问题: 状态变化在模板中不更新
解决方案: 使用 storeToRefs() 代替直接解构
问题: Getter使用 this 有TypeScript错误
解决方案: 显式键入返回值: myGetter(): ReturnType { return this.otherGetter }
问题: $reset() 在设置存储中不可用
解决方案: 手动实现自定义重置:
function $reset() {
count.value = 0
name.value = ''
}
return { count, name, $reset }
问题: HMR不适用于存储
解决方案: 添加HMR接受块:
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useMyStore, import.meta.hot))
}
问题: 测试间歇性失败
解决方案: 在 beforeEach() 中创建新鲜Pinia:
beforeEach(() => {
setActivePinia(createPinia())
})
何时加载参考
加载 references/store-syntax-guide.md 当:
- 需要选项与设置存储语法的详细比较
- 决定新存储使用哪种语法
- 关于选项与设置存储权衡的问题
- 需要两种语法的完整示例
加载 references/state-getters-actions.md 当:
- 需要状态、getters或actions的完整API参考
- 关于
$patch,$subscribe, 或$onAction的问题 - 实现存储组合模式
- 使用Options API映射器 (
mapStores,mapState,mapActions) - 在组件外部访问存储 (路由、插件)
加载 references/plugins-composables.md 当:
- 创建自定义Pinia插件
- 集成VueUse或其他可组合到存储
- 需要持久化、路由或日志插件模式
- 关于插件的TypeScript键入问题
- 高级可组合集成
加载 references/ssr-and-nuxt.md 当:
- 设置服务器端渲染
- 集成与Nuxt 3/4
- 关于状态水合或序列化的问题
- SSR相关错误 (错误Pinia实例、水合不匹配)
- Nuxt自动导入或配置
- 服务器端数据获取模式
加载 references/testing-guide.md 当:
- 设置存储的单元测试
- 测试使用Pinia存储的组件
- 需要存根actions或模拟getters
- 关于
createTestingPinia的问题 - 测试SSR存储
- Vitest或测试框架集成
加载 references/vuex-migration.md 当:
- 迁移现有Vuex代码库到Pinia
- 关于Vuex→Pinia转换的问题
- 需要迁移清单或示例
- 需要逐步迁移策略
完整设置清单
- [ ] 安装
pinia包 - [ ] 用
createPinia()创建Pinia实例 - [ ] 在挂载之前用
app.use(pinia)注册 - [ ] 创建存储目录 (例如,
src/stores/) - [ ] 用
defineStore()定义至少一个存储 - [ ] 在组件解构时使用
storeToRefs() - [ ] 当使用
this时键入getter返回值 - [ ] 添加HMR支持与
acceptHMRUpdate()(开发) - [ ] 配置SSR水合 (如果使用SSR)
- [ ] 配置
@pinia/nuxt(如果使用Nuxt) - [ ] 设置测试与
createTestingPinia()(如果测试) - [ ] 所有存储遵循一致命名:
use[名称]Store - [ ] 验证开发工具集成工作
问题? 问题?
- 检查官方文档: https://pinia.vuejs.org/
- 回顾上方"已知问题预防"部分
- 验证设置清单完整
- 检查TypeScript配置问题
- 确保在使用存储之前安装Pinia