编程 Pinia 4.1.0:六个不报错、只是不生效的地方

2026-10-22 21:04:31

Pinia 4.1.0:六个不报错、只是不生效的地方

Pinia 4.1.0 与 @pinia/testing 2.0.2 于 2026-10-14 发布。仓库在 vuejs/pinia,完整变更见 releases。下面几处问题的共同点是:控制台没有报错,状态看上去也写进去了,只是依赖没有更新,或者清理得不对。

1. 对象语法 $patch 不触发 shallowRef

store 里把大数组或第三方实例包成 shallowRef 是常见做法,为了避免深层代理。问题出在用对象语法 patch 时:值写进去了,依赖却没有被通知。

export const useListStore = defineStore('list', () => {
const items = shallowRef([])
return { items }
})
// 4.1.0 之前:不触发依赖更新
store.$patch({ items: ['a', 'b'] })

4.1.0 修复了这条路径(#2861、#3177)。在旧版本上,或者不想等升级时,有两种写法可以绕开:

// 直接赋值,走 shallowRef 的 setter
store.items = ['a', 'b']

// 函数式 patch,赋值经过响应式对象
store.$patch((state) => {
state.items = ['a', 'b']
})

2. dispose 已被替换的 store 会告警

同一 id 的 store 被新实例替换后,旧引用可能还留在某个闭包里。这时再对旧实例调用 $dispose() 属于误用,此前是静默的,4.1.0 起会给出警告(#3193)。

const store = useUserStore()
// …该 store 被新实例替换
store.$dispose() // 4.1.0:控制台出现警告

要清理就针对当前实例,不要拿旧引用做收尾。

3. getStores() 批量检查与清理

新增的 getStores()(#3194)返回当前 Pinia 实例里注册的 store 集合,适合测试 teardown 和 SSR 请求结束时统一释放。

import { getStores } from 'pinia'

getStores().forEach((store) => store.$dispose())

配合上一条,也不必再自己维护一张「已创建 store」的登记表。

4. HMR 保留空占位对象的运行时状态

setup store 里先用 ref({}) 占位、运行时再往上挂字段,热更新后这些字段以前会丢。4.1.0 保留了空对象、空集合这类占位对象的运行时状态(#2931、#3175)。

export const useConfigStore = defineStore('config', () => {
const config = ref>({})
return { config }
})

// 运行时
useConfigStore().config.theme = 'dark'
// 4.1.0 起,HMR 之后 theme 仍在

5. @pinia/testing 2.0.2:SSR 水合改为替换

reactive 的 Set/Map 状态在 hydration 时,旧行为是和服务端状态做并集,测试里会莫名多出元素。2.0.2 起直接用服务端状态替换(#3162)。

// 客户端水合前已有
store.selected = new Set(['b'])
// 服务端状态为 new Set(['a'])

// 2.0.2 之前:并集 -> Set { 'a', 'b' }
// 2.0.2 起:替换 -> Set { 'a' }

断言要跟着改成替换语义,否则测试仍然通过,但它验证的并不是真实的水合结果。

6. Pinia 4 本体:ESM only 与 storeToRefs

Pinia 4 只包含技术性破坏变更:ESM only,以及 @vue/devtools-api 升到 v8 后必须和 pinia 一起安装。

npm i pinia @vue/devtools-api

storeToRefs 现在会跳过 nullish 值(#3134),并对非法属性在开发期给出诊断;插件的错误值、用 Nostics 重构后的错误与警告信息也更具体。同批还修了 shouldHydrate 对非普通对象上 skipHydrate 标记的识别、同一回调重复订阅导致的重复 watcher,以及 devtools 里可写 getter(computed)的编辑。

迁移检查清单

  • 构建目标切到 ESM,确认没有残留的 CJS require 入口
  • 显式安装 @vue/devtools-api@8
  • 搜一遍所有 $patch({ ... }),确认目标字段不是 shallowRef;是的话改成直接赋值或函数式写法
  • 检查 teardown 里对旧 store 引用的 $dispose(),改用 getStores() 统一清理
  • 用手写的实例登记表换成 getStores()
  • HMR 相关逻辑不再依赖「重新挂字段」的旧行为
  • @pinia/testing 升到 2.0.2,SSR 测试中 Set/Map 断言改为替换语义
  • storeToRefs 解构处允许 nullish 值,去掉为它加的兜底 watch
  • class 实例等非普通对象上的 skipHydrate 仍按预期跳过
  • 同一个 watcher 回调只订阅一次
复制全文 生成海报 Vue Pinia 状态管理 SSR

推荐文章

程序员茄子在线接单