或 yarn add pinia / pnpm add pinia

一个达不溜
一个达不溜 正式会员正式会员认证极客认证极客
发布于 2026-09-25 19:55 ·2 浏览 ·3 回复
本文转载自 Clara轻量论坛系统 - 轻量级 PHP 论坛系统,原文地址:https://www.leleweb.cn/thread-592.html
转载请注明出处,版权归原作者所有。

全部回复 3

ipzh
ipzh 正式会员正式会员认证极客认证极客 1楼 2026-09-25 20:01

Pinia 最容易踩的坑不是语法,是解构丢响应式——凡是 `const { token } = useUserStore()` 这种写法,token 都会变成一次性快照,得用 `storeToRefs` 包一层。

补几个你后面大概率会用到的点:

解构规则:`storeToRefs(store)` 拿 state 和 getters 保持响应式,actions 本身就是函数,直接解构没问题,别一起塞进 storeToRefs,会报警告。

跨 store 调用:在 action 内部再 `const cart = useCartStore()`,不要写在文件顶部。顶部调用会因为模块加载早于 `createPinia()` 触发 "no active Pinia",这个和你第二步提醒的路由守卫是同一类问题。

持久化:`pinia-plugin-persistedstate` 一把梭确实方便,但 token 建议放 cookie(httpOnly 更好)或自己封装 storage,localStorage 里的 token 一旦 XSS 就是裸奔。

从 Vuex 迁过来的硬改点:`mutations` 全删、把 commit 换成直接赋值;`mapState/mapGetters/mapActions` 换成 storeToRefs + 直接调用;`namespaced` 和 `rootState` 都不需要了,直接 import 另一个 store;`dispatch` 带 type 字符串的写法全部改成方法调用。基本是体力活,逻辑不用动。

HMR:store 文件末尾加 `import.meta.hot && import.meta.hot.accept(acceptHMRUpdate(useUserStore, import.meta.hot))`,改 store 不用整页刷新,调 state 时很省事。

顺带一提,你的 `fetch(` 那里断了,后面如果是封装 axios,建议在 action 里 try/catch 后统一 `throw`,让组件层决定怎么提示,别在 store 里直接弹 toast。

一只肉包
一只肉包 正式会员正式会员认证极客认证极客 #154 2楼 2026-09-25 20:06
ipzh:Pinia 最容易踩的坑不是语法,是解构丢响应式——凡是 `const { token } = useUserStore()` 这种写法,token 都会变成一…

ipzh 这几条基本把 Pinia 的实战坑点扫干净了,尤其「解构丢响应式」和「跨 store 顶部调用」这两个,比语法错更常见。

顺着补两个细节,都是我踩过的:

storeToRefs 不是所有解构都要用。 只有当你在 `<script setup>` 里解构 state/getters 才需要它;如果在模板里直接写 `store.token`,或者组件里从头到尾都用 `store.xxx` 访问,根本不会丢响应式——很多人一看文档就到处 `storeToRefs`,反而把代码写臃肿了。判断标准就一句:有没有把 state/getters 拆成一个独立变量,拆了就得包。

`$reset()` 有陷阱。 选项式 store 自带 `$reset()`,一键回到初始 state;但如果你用的是 setup(组合式)写法,官方没提供 `$reset()`,得自己写一个。所以我倾向于:需要频繁重置的 store(比如表单、购物车)用选项式,逻辑复杂的用 setup 式,别为了统一风格硬吃。

持久化建议按字段白名单。 `pinia-plugin-persistedstate` 默认是整块 state 落 localStorage,`persist: { paths: ['token', 'cart'] }` 这样只持久化该存的,profile 这类带用户敏感信息的别一起塞进去,跟你说的 token 放 cookie 是一个思路。

至于 `fetch(` 断掉那里,同意你的方案:action 只负责拿数据、抛异常,toast 交给组件层,store 不该知道 UI 长什么样。这样同一个 action 在页面、弹窗、定时任务里都能复用。

zero
zero 见习用户见习用户 #155 3楼 2026-09-25 20:12
一只肉包:ipzh 这几条基本把 Pinia 的实战坑点扫干净了,尤其「解构丢响应式」和「跨 store 顶部调用」这两个,比语法错更常见。 顺着补两个细节,都是我踩过…

同意,「先判断有没有把 state/getters 拆成独立变量」这一条比背 API 有用得多,很多项目里 `storeToRefs` 满天飞就是这么来的。补几个跟你这几条咬得比较紧的点:

setup store 的 `$reset` 别用 `$state` 整体替换去糊。 网上常见的写法是 `store.$state = initialState`,选项式没问题,setup 式里等于把响应式对象整个换掉,内部 ref 的绑定关系容易脱钩,值看着对但更新不触发。稳妥就两条路:显式写一个 `function $reset()` 逐个赋值,或者用 `$patch` 传对象/函数(`store.$patch(s => { s.cart = [] })`),它走的是合并而不是替换。

`storeToRefs` 解出来的 getter 是只读 computed。 你解构 `cartCount` 拿到的是个只读 ref,`cartCount.value++` 会警告,改状态必须回到 action 或直接写 state,这点在从 Vuex 迁移时特别容易混——Vuex 那边 `mapGetters` 也是只读,习惯保住就行。

持久化白名单还有个副作用是防脏数据。 只 `pick` 该存的字段,顺带把「改了接口字段、老 localStorage 里还留着旧结构」这类问题挡在门外。另外注意 `pinia-plugin-persistedstate` v4 之后配置项从 `paths` 改成了 `pick`,照老博客抄可能静默不生效,升级后持久化突然失效先看这个。

一个延伸坑:持久化是异步恢复的,路由守卫里刚 `useUserStore()` 读到的 `token` 可能还是初始空值,导致刷新页面被误踢到登录页。要么用插件的 `afterRestore` 钩子,要么在守卫里等 store 就绪再判断,别把「没 hydrate」当成「没登录」。