Piniav3-Vue状态管理 pinia-v3

Pinia v3是Vue.js的官方状态管理库,用于在Vue 3应用中管理全局状态。它支持defineStore、getters、actions等功能,适用于Vue 3存储、Nuxt SSR、Vuex迁移、存储组合、状态水合、测试等场景。关键词:Pinia, Vue状态管理, defineStore, 状态管理, Vue 3, SSR, Nuxt, 测试, 插件, 可组合。

前端开发 0 次安装 0 次浏览 更新于 3/7/2026

名称: 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 获取完整迁移指南。

快速转换概述

关键变化:

  1. 移除 namespaced (通过存储ID自动)
  2. 消除 mutations (直接状态突变)
  3. 替换 commit() 为直接突变
  4. 替换 rootState/rootGetters 为存储导入
  5. 使用 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 (在组件外部访问存储)

官方文档


故障排除

问题: “getActivePinia() was called with no active Pinia”

解决方案:

  1. 确保在挂载之前调用 app.use(pinia)
  2. 如果在组件外部,在回调/函数内部调用 useStore()
  3. 对于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
  • [ ] 验证开发工具集成工作

问题? 问题?

  1. 检查官方文档: https://pinia.vuejs.org/
  2. 回顾上方"已知问题预防"部分
  3. 验证设置清单完整
  4. 检查TypeScript配置问题
  5. 确保在使用存储之前安装Pinia