airi 中的 Pinia 与 Nuxt 3/4 集成:自动导入、SSR 数据获取与插件扩展实践
【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi
本文基于 airi 仓库内置的 Pinia 技能参考文档 advanced-nuxt.md,完整讲解@pinia/nuxt的安装配置、自动导入机制、callOnceSSR 数据获取、组件外使用 store 以及 Nuxt 插件式扩展 Pinia 的五类核心场景,并结合 airi 单仓中真实的 Pinia 装配源码,帮助你在 Nuxt 与纯 Vite SPA 两种工程形态下都掌握 Pinia 的正确接线方式。
文档背景:它在 airi 技能体系中的位置
这篇集成文档位于 .agents/skills/pinia/references/advanced-nuxt.md,是 Pinia 技能入口 下 “Advanced” 主题之一,与 SSR 状态水合、HMR 支持 和 插件机制 等参考文档并列。技能入口中注明其基于 Pinia v3.0.4、生成于 2026-01-28;而 airi 工作区在 pnpm-workspace.yaml 中通过 catalog 统一锁定了pinia: ^4.0.3,并同时引入了@pinia/colada(数据获取)与@pinia/testing(测试)。因此本文内容适用于 Nuxt 3/4 + Pinia 3/4 时代的项目;阅读下文配置示例时,请以当前仓库实际依赖版本为准。
安装:一条 nuxi 命令与 npm ERESOLVE 陷阱
Pinia 与 Nuxt 3/4 的集成由官方 Nuxt 模块@pinia/nuxt承担,SSR、状态序列化与 XSS 防护均被模块自动处理。标准安装方式为:
npx nuxi@latest module add pinia该命令会同时安装@pinia/nuxt与pinia两个包;如果本地已有旧版pinia残留,需要手动补齐缺失的一方。
文档特别提示了一个 npm 用户常见坑:若安装时报ERESOLVE unable to resolve dependency tree,应在package.json中声明依赖覆盖:
"overrides": { "vue": "latest" }这一点在 airi 仓库有对应的工程语境:airi 使用 pnpm workspace 而非 npm overrides,版本一致性改由 catalog 保证——pnpm-workspace.yaml 中pinia: ^4.0.3、@pinia/colada: ^1.4.2、@pinia/testing: ^2.0.1等条目被各 app 与 package 以"pinia": "catalog:"形式引用(如 apps/stage-web/package.json、packages/stage-ui/package.json)。两种做法目的相同:避免多个 Vue/Pinia 副本并存导致的响应式断裂。
nuxt.config.ts 配置与自动导入
最小配置
在nuxt.config.ts中注册模块即可:
// nuxt.config.ts export default defineNuxtConfig({ modules: ['@pinia/nuxt'], })模块自动提供的 API
启用模块后,以下 API 无需手动 import,在<script setup>中直接可用:
usePinia()— 获取当前 pinia 实例;defineStore()— 定义 store;storeToRefs()— 从 store 解构并保持响应性的 refs;acceptHMRUpdate()— 开发期热更新支持。
更关键的是store 的自动导入:app/stores/(Nuxt 4 目录约定)或stores/下的所有 store 会被模块自动导入,页面中可直接useXxxStore()而无需导入语句。这与 airi 的 Vite SPA 形成鲜明对照——apps/stage-web/src/stores 下的background.ts、devtools-lag.ts、pwa.ts都需要在组件中显式导入使用,Nuxt 模块省掉的正是这类样板代码。
自定义 Store 目录
当 store 分散在多个目录时,可用storesDirs配置多个 glob:
// nuxt.config.ts export default defineNuxtConfig({ modules: ['@pinia/nuxt'], pinia: { storesDirs: ['./stores/**', './custom-folder/stores/**'], }, })该配置让模块把额外的目录纳入自动导入扫描范围,适合将领域 store 按业务模块组织的单仓项目。
SSR 友好的数据获取:callOnce
页面中获取数据时,推荐使用模块提供的callOnce(),它在 SSR 与水合阶段天然安全:
<script setup> const store = useStore() // 只运行一次,数据在后续导航间持久保留 await callOnce('user', () => store.fetchUser()) </script>第一个参数是缓存键,callOnce以该键保证同一请求在整个应用会话中只执行一次,导航到其它页面再回来时直接使用已缓存的结果。若业务上希望“每次导航都重新拉取”(行为对齐 Nuxt 的useFetch),则传入navigation模式:
<script setup> const store = useStore() // 每次导航都重新获取(类似 useFetch) await callOnce('user', () => store.fetchUser(), { mode: 'navigation' }) </script>两者覆盖了“会话级一次性数据”与“导航级新鲜数据”两类最常见的服务端取数语义。
在组件之外使用 Store:显式传递 pinia 实例
在导航守卫、中间件或普通工具函数中,没有 Vue 注入上下文,store 无法自动解析 pinia 实例。此时需从useNuxtApp()取出$pinia显式传入。文档给出的路由中间件示例:
// middleware/auth.ts export default defineNuxtRouteMiddleware((to) => { const nuxtApp = useNuxtApp() const store = useStore(nuxtApp.$pinia) if (to.meta.requiresAuth && !store.isLoggedIn) { return navigateTo('/login') } })文档同时强调:大多数场景不需要这样做——在组件或任何有注入能力的上下文中,直接调用useStore()即可。
在 Nuxt 中使用 Pinia 插件
@pinia/nuxt把 pinia 实例挂载为nuxtApp.$pinia,因此注册 Pinia 插件的自然位置是一个 Nuxt 插件。文档示例:
// plugins/myPiniaPlugin.ts import { PiniaPluginContext } from 'pinia' function MyPiniaPlugin({ store }: PiniaPluginContext) { store.$subscribe((mutation) => { console.log(`[🍍 ${mutation.storeId}]: ${mutation.type}`) }) return { creationTime: new Date() } } export default defineNuxtPlugin(({ $pinia }) => { $pinia.use(MyPiniaPlugin) })要点有两处:一是利用store.$subscribe监听 mutation 做日志/埋点,二是插件函数可以返回额外属性(如creationTime)合并进 store。
airi 单仓中的对应实践:手动装配等价链路
airi 的各端应用是 Vite + Vue SPA(而非 Nuxt),但走的是同一套pinia.use()插件机制。以 apps/stage-web/src/main.ts 为例:
const pinia = createPinia() const synced = setupSynced() pinia.use(synced.pinia) if (import.meta.env.DEV) pinia.use(piniaPluginTracing)随后在createApp(App).use(pinia).use(PiniaColada)中先装 pinia 再装@pinia/colada插件——插件注册顺序(必须在 pinia 插件之后)与 Nuxt 插件写法中的$pinia.use是同一约束。这里的synced.pinia来自 packages/stage-ui/src/libs/pinia/setup-synced.ts,它封装了pinia-plugin-synced库,配置了命名空间airi:stage:pinia与 5 分钟的callTimeout,用于在 Stage 的多渲染窗口之间做状态快照复制与领导者路由调用;仓库 AGENTS.md 也明确将其定位为“快照复制 + 领导者路由 RPC,不跨渲染器共享 Vue refs”。从源码结构看,这段手动装配等价于@pinia/nuxt在 Nuxt 项目中自动完成的工作:创建实例、按序挂插件、注入到应用。此外开发环境特有的pinia.use(piniaPluginTracing)也示范了插件“按环境条件挂载”的常见写法。
要点回顾
- 安装用
npx nuxi@latest module add pinia;npm 遇到ERESOLVE时用overrides固定vue版本。 @pinia/nuxt提供usePinia/defineStore/storeToRefs/acceptHMRUpdate的自动导入,并自动导入app/stores/(Nuxt 4)或stores/下的全部 store;多目录场景用pinia.storesDirs扩展。- 页面取数用
callOnce控制“一次执行”与“按导航刷新”两种语义。 - 组件外(中间件、守卫)使用 store 时通过
useNuxtApp().$pinia显式传入实例。 - Pinia 插件通过 Nuxt 插件中的
$pinia.use()注册;airi 在纯 SPA 工程中的createPinia+pinia.use(...)装配是同一机制的手动版本,两者可互相参照理解。
【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考