首页
/ airi 项目实战:用 Pinia HMR 保留 Store 状态的热更新开发指南

airi 项目实战:用 Pinia HMR 保留 Store 状态的热更新开发指南

2026-09-05 20:04:52作者:钟日瑜

本文基于 airi 仓库中随 Agent 技能库提供的 Pinia 参考资料 advanced-hmr.md 展开,讲解 Pinia 热模块替换(Hot Module Replacement, HMR)的完整接入方式:如何在 store 定义后追加 HMR 片段、acceptHMRUpdate 的作用、Vite / Webpack 等打包器的支持差异,以及在 Nuxt 中的注意事项。读完后你可以照搬代码片段,让 airi 这类 Vite + Pinia 的 Vue 应用在开发阶段修改 store 逻辑时无需整页刷新、状态不丢失。

为什么需要 Store 级 HMR

Pinia 支持 HMR,让开发者可以在不重新加载页面的情况下编辑 store,并保留已有的运行时状态。

对 airi 这类状态复杂的桌面/Web 应用来说这一点尤为关键:从源码结构看,仓库中大量核心 store 都是 Setup Store 风格,例如 useBackgroundStore 通过 defineStore('background', () => { ... }) 定义,内部维护了 refuseLocalStorage 本地持久化、对象 URL 缓存(blobRefs / urlRefs)以及 IndexedDB 加载逻辑。这类 store 一旦刷新页面,诸如已加载的背景图片缓存、选中项等状态都要重新初始化。接入 HMR 后,修改 store 内部逻辑(新增 getter、调整 action 实现)时可以直接热替换模块,避免反复刷新带来的状态重置和交互成本。

接入方式:在 store 定义后追加 HMR 片段

根据 advanced-hmr.md 的说明,做法是在每一个 store 定义之后追加一段条件注册代码。核心 API 有两个:

  • defineStore:定义 store(Pinia 标准 API);
  • acceptHMRUpdate:来自 pinia 包,负责把旧模块的运行时状态迁移到新模块实例,返回一个 import.meta.hot.accept 的处理器。

Options Store 写法

import { defineStore, acceptHMRUpdate } from 'pinia'

export const useAuth = defineStore('auth', {
  // store options...
})

if (import.meta.hot) {
  import.meta.hot.accept(acceptHMRUpdate(useAuth, import.meta.hot))
}

Setup Store 写法

airi 仓库实际采用的正是 Setup Store 风格(如 background.tsdefineStore('background', () => {...})),对应的 HMR 片段示例如下:

import { defineStore, acceptHMRUpdate } from 'pinia'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const increment = () => count.value++
  return { count, increment }
})

if (import.meta.hot) {
  import.meta.hot.accept(acceptHMRUpdate(useCounterStore, import.meta.hot))
}

两个示例的差别仅在于 store 定义形式(options 对象 vs setup 函数),HMR 尾部片段完全一致:acceptHMRUpdate(store, import.meta.hot) 会在模块被热替换时,将当前活跃实例中已有的 state 拷贝到新的 store 定义上,并注册该模块为 HMR 边界(bundler boundary),使 Vite 能就地替换而不向上冒泡到整页刷新。

background.ts 为例,如果要在 airi 仓库的实际 store 中落地该能力,只需在文件末尾 }) 之后补上 if (import.meta.hot) { ... } 片段,并将 pinia 的 import 从 import { defineStore } from 'pinia' 扩为 import { acceptHMRUpdate, defineStore } from 'pinia'。从源码结构看,仓库当前各 store 文件(如该文件)尚未统一挂载 acceptHMRUpdate 片段,上述资料文档正是为此准备的接入规范。

打包器支持矩阵

原文档列出了三类打包器环境的支持情况:

  • Vite:通过 import.meta.hot 官方支持。airi 的多个前端应用(stage-webstage-tamagotchi 等)都基于 Vite 构建,且入口已经在使用 import.meta.hot——例如 stage-tamagotchi 渲染进程入口 中就有 if (import.meta.hot) { handleHotUpdate(router, ...) } 用于路由表热更新,说明 import.meta.hot 在该代码库中是可用的标准能力,Pinia store 的 HMR 片段可以无缝共存。
  • Webpack:使用 import.meta.webpackHot
  • 其他打包器:任何实现了 import.meta.hot 规范的 bundler 理论上都可以工作。这也是为什么片段中要用 if (import.meta.hot) 做存在性判断——在生产构建(HMR 不注入)或 Webpack 环境下该判断可以安全跳过,不会报错。

Nuxt 环境下的注意事项

文档特别指出:在 Nuxt 项目中配合 @pinia/nuxt 使用时,acceptHMRUpdate 会被自动导入(auto-import),但 HMR 片段本身仍需要手动添加到每个 store 之后。也就是说 Nuxt 只省去了 import 声明,热替换注册这一步不会自动完成。airi 仓库的 Web/桌面应用并不使用 Nuxt(stage-tamagotchi 入口 中可以看到显式 createPinia() 并手动 .use(pinia) 的模式),因此这条主要对使用 Nuxt 的读者有意义。

HMR 带来的实际收益

原文档总结了三点收益,结合仓库的 store 复杂度可以逐条印证:

  • 编辑 store 逻辑时不丢状态:修改 useBackgroundStore 内的计算逻辑时,已加载的背景选项与选中项得以保留,不用重新走一遍 IndexedDB 加载流程;
  • 随时增删 state、actions、getters:Setup Store 返回对象变化(如给 background.ts 的 return 中新增一个方法)可以在热替换中直接生效;
  • 更快的开发迭代:修改 store 后不再等待整页重载,这对 stage-tamagotchi 这类同时挂载 3D 渲染、音频管线的重型应用尤其有价值——刷新意味着 WebGL 上下文与资源重新初始化。

版本与适用前提

  • 本技能资料基于 Pinia v3.0.4 生成(见 SKILL.md 中 "The skill is based on Pinia v3.0.4" 的说明);
  • airi 仓库当前通过 pnpm catalog 统一声明 pinia: ^4.0.3(见 pnpm-workspace.yaml),各应用以 "pinia": "catalog:" 引用(如 stage-web/package.json)。acceptHMRUpdate 自 Pinia 早期版本即为公开 API,在 v4 中同样可用;
  • 前提条件:应用入口必须已经 app.use(pinia) 完成初始化,且开发服务器为支持 HMR 的环境(Vite dev server 或等效实现);生产构建中 import.meta.hot 为 undefined,片段自动不生效,无任何运行时开销。

参考文件

内容 路径
本文主体参考文档(Setup / Bundler / Nuxt / Benefits) .agents/skills/pinia/references/advanced-hmr.md
Pinia 技能索引(Advanced 分类入口) .agents/skills/pinia/SKILL.md
仓库实际 Setup Store 示例 packages/stage-layouts/src/stores/background.ts
应用入口的 createPiniaimport.meta.hot 用法 apps/stage-tamagotchi/src/renderer/main.ts
Pinia 版本声明(catalog: ^4.0.3 pnpm-workspace.yaml
登录后查看全文
热门项目推荐
相关项目推荐