mcpField
P
pinia-v3

pinia-v3

github.com/secondsky开发240230 次安装5 周前更新
代码仓库

关于 pinia-v3

面向 Vue 和 Nuxt 开发者的 Pinia v3 技能指南,介绍 Vue 状态管理。内容涵盖 Store 模式、SSR、测试、HMR,以及从 Vuex 迁移的指导。

主要功能

  1. 01使用两种语法定义 Store
  2. 02管理状态、Getter 和 Action
  3. 03集成插件与组合式函数
  4. 04处理 SSR 和 Nuxt 状态水合
  5. 05测试 Store 和组件
  6. 06配置 HMR 并从 Vuex 迁移

适用场景

  1. 01组织 Vue 应用中的共享状态
  2. 02在 SSR 应用中水合 Store
  3. 03使用隔离状态测试 Store
  4. 04将 Vuex 模块迁移至 Pinia

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/secondsky/claude-skills --skill pinia-v3

安装后在 Claude Code 中输入/pinia-v3确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

assets/
example-template.txt
references/
example-reference.md
plugins-composables.md
ssr-and-nuxt.md
state-getters-actions.md
store-syntax-guide.md
testing-guide.md
vuex-migration-checklist.md
vuex-migration.md
scripts/
api-store-example.ts
auth-store-example.ts
example-script.sh
option-store-template.ts
persistence-plugin.ts
setup-store-template.ts

详细介绍

Pinia v3 - Vue 状态管理

状态:已可用于生产 ✅ 最近更新:2025-11-11 依赖项:Vue 3(或搭配 @vue/composition-api 的 Vue 2.7) 最新版本:pinia@^3.0.4、@pinia/nuxt@^0.11.2、@pinia/testing@^1.0.2


快速开始(5 分钟)

1. 安装 Pinia
bun add pinia
# or
bun add pinia
# or
bun add pinia

Vue <2.7 用户:还需使用 bun add @vue/composition-api 安装 @vue/composition-api

为什么这很重要:

  • Pinia 是 Vue 官方的状态管理库
  • 提供比 Vuex 更好的 TypeScript 支持
  • 消除 mutations 和命名空间带来的复杂性
  • 完整支持 DevTools,并提供时间旅行调试
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')

重要:

  • 使用任何 store 之前,先安装 Pinia
  • 挂载应用之前,调用 app.use(pinia)
  • 每个应用只能有一个 Pinia 实例(SSR 除外)
3. 定义你的第一个 Store
// 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. 在组件中使用 Store
<script setup>
import { useCounterStore } from '@/stores/counter'

const counter = useCounterStore()
</script>

<template>
  <div>
    <p>Count: {{ counter.count }}</p>
    <p>Double: {{ counter.doubleCount }}</p>
    <button @click="counter.increment">Increment</button>
  </div>
</template>

两种 Store 语法

加载 references/store-syntax-guide.md,查看 Option Store 与 Setup Store 的完整对比。

快速概览

Pinia 支持两种 Store 定义语法:

Option Store:

  • 类似于 Vue Options API
  • 内置 $reset() 方法
  • 适用于:较简单的使用场景,以及熟悉 Vuex 的团队

Setup Store:

  • 使用 Composition API 模式
  • 完整集成组合式函数
  • 适用于:高级模式,以及需要集成侦听器/VueUse 的场景

→ 加载 references/store-syntax-guide.md,查看: 完整的语法对比、示例和选择标准


State、Getters 和 Actions

加载 references/state-getters-actions.md,查看完整的 API 参考。

快速参考

State:

  • 在 state: () => ({...})(Option)或 ref()(Setup)中定义
  • 直接访问:store.count
  • 直接修改:store.count++ 或 store.$patch({...})
  • 重置:store.$reset()(仅限 Option Store)

Getters:

  • 计算属性:getters: { double: (state) => state.count * 2 }
  • 使用 this 访问其他 getter(必须为返回值指定类型)

Actions:

  • 业务逻辑:actions: { increment() { this.count++ } }
  • 可以是异步的
  • 可以直接访问其他 Store

Store 解构:

import { storeToRefs } from 'pinia'

// ✅ For reactivity
const { name, count } = storeToRefs(store)

// ✅ Actions can destructure directly
const { increment } = store

→ 加载 references/state-getters-actions.md,查看: 完整 API、订阅、Store 组合模式、Options API 用法


插件和组合式函数

加载 references/plugins-composables.md,查看完整的插件和组合式函数指南。

插件基础
pinia.use(({ store, options }) => {
  // Add properties to every store
  return { customProperty: 'value' }
})
组合式函数集成

Option Store: 仅限在 state() 中使用 useLocalStorage 这类函数
Setup Store: 完整支持 VueUse/组合式函数

→ 加载 references/plugins-composables.md,查看: 完整的插件模式、VueUse 集成、TypeScript 类型、常见模式(持久化、路由器、日志记录器)


在组件外使用 Store

问题

Store 需要 Pinia 实例。该实例会自动注入组件,但在模块作用域中不可用。

❌ 错误:在模块级别访问 Store
// router.ts
import { useUserStore } from '@/stores/user'

// ❌ Fails: Pinia not installed yet
const userStore = useUserStore()

router.beforeEach((to) => {
  if (userStore.isLoggedIn) { /* ... */ }
})
✅ 正确:在回调中访问 Store
// router.ts
import { useUserStore } from '@/stores/user'

router.beforeEach((to) => {
  // ✅ Works: Called after Pinia is installed
  const userStore = useUserStore()

  if (userStore.isLoggedIn) { /* ... */ }
})

这样做可行的原因:路由器守卫会在 app.use(pinia) 执行完成后运行。

SSR:显式传入 Pinia 实例
// server-side
export function setupRouter(pinia) {
  router.beforeEach((to) => {
    const userStore = useUserStore(pinia) // Pass explicitly
  })
}

服务端渲染与 Nuxt

加载 references/ssr-and-nuxt.md,查看完整的 SSR 和 Nuxt 集成指南。

SSR 快速参考

State 水合:

  • 服务端:使用 devalue() 序列化(不要使用 JSON.stringify)
  • 客户端:在调用 useStore() 之前完成水合
  • 重要:在 actions 中的 await 之前调用所有 useStore()
Nuxt 3/4 集成
bunx nuxi@latest module add pinia

自动导入: defineStore、storeToRefs、usePinia、acceptHMRUpdate、所有 Store

→ 加载 references/ssr-and-nuxt.md,查看: 完整的 SSR 模式、Nuxt 配置、服务端数据获取、SSR 常见问题、调试


测试

加载 references/testing-guide.md,查看完整的测试指南。

测试快速开始
import { setActivePinia, createPinia } from 'pinia'

beforeEach(() => {
  setActivePinia(createPinia()) // Fresh Pinia for each test
})
组件测试
bun add -d @pinia/testing
import { createTestingPinia } from '@pinia/testing'

mount(Component, {
  global: { plugins: [createTestingPinia()] }
})

→ 加载 references/testing-guide.md,了解: 完整的测试模式、操作存根、getter 模拟、异步测试、SSR 测试


热模块替换(HMR)

Vite 配置
// stores/counter.ts
import { defineStore, acceptHMRUpdate } from 'pinia'

export const useCounterStore = defineStore('counter', {
  // store definition
})

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))
}

优势:

  • 编辑 store,无需完整重新加载页面
  • 在开发期间保留应用状态
  • 加快开发迭代

Options API 用法

对于仍在使用 Options API 的项目,请加载完整的 mapper 文档。

→ 加载 references/state-getters-actions.md,了解: 完整的 Options API 集成、所有 mapper(mapStores、mapState、mapWritableState、mapActions)


从 Vuex 迁移

加载 references/vuex-migration.md,查看完整的迁移指南。

快速转换概览

主要变更:

  1. 移除 namespaced(通过 store ID 自动实现)
  2. 移除 mutations(直接修改状态)
  3. 将 commit() 替换为直接修改
  4. 将 rootState/rootGetters 替换为 store 导入
  5. 使用 store.$reset() 替代自定义的清除 mutation

目录: store/modules/ → stores/(每个模块 = 独立的 store)

→ 加载 references/vuex-migration.md,了解: 完整的转换步骤、组件迁移、检查清单、渐进式迁移策略


关键规则

务必执行

✅ 在 state() 中定义所有状态属性,或从 setup store 中返回这些属性 ✅ 在组件中解构状态/getter 时使用 storeToRefs() ✅ 在挂载应用之前调用 app.use(pinia) ✅ 从 setup store 中返回所有状态(私有状态会破坏 SSR/DevTools) ✅ 在组件外使用时,在函数/回调中调用 useStore() ✅ 使用 acceptHMRUpdate() 支持开发时的 HMR ✅ getter 使用 this 访问其他 getter 时,注明返回值类型 ✅ 使用 devalue 序列化 SSR 状态(防止 XSS) ✅ 在客户端调用任何 useStore() 之前,先 hydrate 状态(SSR) ✅ 在异步 action 中的任何 await 之前调用所有 useStore()(SSR)

切勿执行

❌ 在创建 store 后动态添加状态属性 ❌ 不使用 storeToRefs() 就直接解构 store(会失去响应性) ❌ 将箭头函数用于 action(需要 this 上下文) ❌ 在 setup store 中返回私有状态(会破坏 SSR/DevTools/插件) ❌ 在模块顶层调用 useStore()(Pinia 安装前) ❌ 在 store 之间创建循环依赖(双方都读取对方的状态) ❌ 使用 JSON.stringify() 序列化 SSR 状态(容易受到 XSS 攻击) ❌ 在 action 的 await 之后调用 useStore()(会破坏 SSR) ❌ 使用 this 时忘记注明 getter 的返回值类型 ❌ 在单元测试中省略 beforeEach(() => setActivePinia(createPinia()))


避免已知问题

此技能可避免 12 个有记录的问题:

问题 #1:直接解构导致响应性丢失

错误:解构后,模板中的状态变更不会更新 原因:JavaScript 解构会破坏 Vue 的响应性 预防:始终对状态/getter 使用 storeToRefs()

问题 #2:无法动态添加状态属性

错误:创建 store 后添加的新属性不具备响应性 原因:Pinia 需要预先定义所有属性以实现响应性 预防:在 state() 中声明所有属性,即使初始值为 undefined

问题 #3:Pinia 安装前找不到 Store

错误:getActivePinia() 返回 undefined 原因:在 app.use(pinia) 之前调用 useStore() 预防:在挂载应用或访问 store 之前调用 app.use(pinia)

问题 #4:Setup Store 的私有状态会破坏 SSR

错误:SSR 中的状态未能正确序列化/水合 原因:未从 setup 返回的属性不会被跟踪 预防:从 setup store 中返回所有状态属性

问题 #5:使用 this 的 Getter 无法推断类型

错误:Getter 使用 this 时,TypeScript 无法推断返回类型 来源:Pinia 已知的 TypeScript 限制 预防方法:显式指定返回值类型:getterName(): ReturnType { ... }

问题 #6:Options API Store 后缀混淆

错误:在组件中找不到 this.counterStore 原因:mapStores() 会自动添加 'Store' 后缀 预防方法:使用 Store 名称 + 'Store',或调用 setMapStoreSuffix()

问题 #7:await 后调用 Actions 会导致 SSR 出错

错误:SSR 使用了错误的 Pinia 实例,导致状态污染 原因:await 会改变异步函数中的执行上下文 预防方法:在任何 await 语句之前调用所有 useStore()

问题 #8:Store 循环依赖会导致应用崩溃

错误:超出最大调用堆栈 原因:两个 Store 在初始化期间读取彼此的状态 预防方法:通过 Getter/Action 访问其他 Store,而不是在 setup 阶段读取

问题 #9:SSR 状态序列化中的 XSS 漏洞

错误:状态中的用户输入可能会执行恶意脚本 原因:JSON.stringify() 不会转义可执行代码 预防方法:使用 devalue 库进行安全序列化

问题 #10:开发环境中的 HMR 不起作用

错误:修改 Store 后需要完整重新加载页面 原因:未为 Store 配置 Vite/webpack HMR 预防方法:在每个 Store 文件中添加 acceptHMRUpdate() 代码块

问题 #11:Composables 返回的函数会破坏 Option Store

错误:Store 状态包含无法序列化的函数 原因:Option Store 的 state() 只能返回可写的 refs 预防方法:复杂的 Composables 使用 setup Store,或仅提取可写状态

问题 #12:单元测试之间的状态未重置

错误:测试相互影响,偶尔失败 原因:多个测试共享同一个 Pinia 实例 预防方法:在测试套件中使用 beforeEach(() => setActivePinia(createPinia()))


包版本(已于 2026-08-03 验证)

核心: pinia@^3.0.4、vue@^3.5.24 Nuxt: @pinia/nuxt@^0.11.2、nuxt@^4.0.0(Nuxt 3 已于 2026-07-31 结束生命周期;v4 是当前稳定版本系列) 测试: @pinia/testing@^1.0.2、vitest@^2.0.0 SSR: devalue@^5.3.2(用于安全序列化)


常见模式

完整的模式示例请参阅参考文件:

  • 身份验证 Store → references/state-getters-actions.md
  • 持久化插件 → references/plugins-composables.md
  • 表单 Store → references/store-syntax-guide.md(setup Store 示例)
  • 路由器集成 → references/state-getters-actions.md(在组件外访问 Store)

官方文档


故障排除

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

解决方案:

  1. 确保在挂载之前调用 app.use(pinia)
  2. 如果在组件外部调用,请在回调/函数内部调用 useStore()
  3. 对于 SSR,请显式传入 Pinia 实例:useStore(pinia)
问题:状态更改不会更新模板

解决方案:使用 storeToRefs(),而不是直接解构

问题:使用 this 的 Getter 出现 TypeScript 错误

解决方案:显式指定返回值类型:myGetter(): ReturnType { return this.otherGetter }

问题:setup Store 中无法使用 $reset()

解决方案:手动实现自定义重置:

function $reset() {
  count.value = 0
  name.value = ''
}
return { count, name, $reset }
问题:Store 的 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:

  • 需要详细比较 Option 和 Setup store 语法
  • 决定新 store 应使用哪种语法
  • 有关 Option 和 Setup store 权衡的问题
  • 需要两种语法的完整示例

在以下情况下加载 references/state-getters-actions.md:

  • 需要 state、getter 或 action 的完整 API 参考
  • 有关 $patch、$subscribe 或 $onAction 的问题
  • 实现 store 组合模式
  • 使用 Options API 映射器(mapStores、mapState、mapActions)
  • 在组件外访问 store(路由器、插件)

在以下情况下加载 references/plugins-composables.md:

  • 创建自定义 Pinia 插件
  • 将 VueUse 或其他组合式函数集成到 store 中
  • 需要持久化、路由或日志插件模式
  • 有关插件 TypeScript 类型的问题
  • 高级组合式函数集成

在以下情况下加载 references/ssr-and-nuxt.md:

  • 设置服务端渲染
  • 与 Nuxt 3/4 集成
  • 有关状态水合或序列化的问题
  • SSR 相关错误(Pinia 实例错误、水合不匹配)
  • Nuxt 自动导入或配置
  • 服务端数据获取模式

在以下情况下加载 references/testing-guide.md:

  • 为 store 设置单元测试
  • 测试使用 Pinia store 的组件
  • 需要 stub action 或模拟 getter
  • 有关 createTestingPinia 的问题
  • 测试 SSR store
  • Vitest 或测试框架集成

在以下情况下加载 references/vuex-migration.md:

  • 将现有 Vuex 代码库迁移到 Pinia
  • 有关 Vuex→Pinia 转换的问题
  • 需要迁移清单或示例
  • 需要渐进式迁移策略

完整设置清单

  • 已安装 pinia 包
  • 已使用 createPinia() 创建 Pinia 实例
  • 已在挂载前通过 app.use(pinia) 注册
  • 已创建 store 目录(例如 src/stores/)
  • 已使用 defineStore() 定义至少一个 store
  • 在组件中解构时已使用 storeToRefs()
  • 使用 this 时已为 getter 返回值添加类型
  • 已通过 acceptHMRUpdate() 添加 HMR 支持(开发环境)
  • 已配置 SSR 水合(如果使用 SSR)
  • 已配置 @pinia/nuxt(如果使用 Nuxt)
  • 已通过 createTestingPinia() 设置测试(如果需要测试)
  • 所有 store 均遵循一致的命名方式:use[Name]Store
  • 已验证 DevTools 集成正常工作

有疑问?遇到问题?

  1. 查看官方文档:https://pinia.vuejs.org/
  2. 查看上方的“已知问题预防”部分
  3. 确认已完成设置清单
  4. 检查 TypeScript 配置问题
  5. 确保在使用 store 前已安装 Pinia

评论

支持 Markdown