为什么我放弃使用 Pinia?

Vue3 出来后,Pinia 成了"官方推荐状态管理",文档清爽,API 看上去现代化,我第一时间把原项目从 Vuex 切成了 Pinia。

起初一切顺利------setup 写法简洁,类型提示优秀,组件用起来也比老 Vuex 舒服。

但到了项目第 2 个月,我开始频繁在脑中冒出一句话:

"我当初为什么要换掉 Vuex?"


Pinia 真正的问题:看上去简单,实际不适合复杂项目

一开始的惊艳 来自它那些极简 API:defineStorestateactionsgetters

你可以这么写一个模块:

javascript 复制代码
export const useUserStore = defineStore('user', {
  state: () => ({ name: '', token: '' }),
  actions: {
    login(payload) {
      // 调接口,然后存数据
    }
  }
})

你在组件中就这么用:

php 复制代码
const user = useUserStore()
user.login({ name: 'jack' })

到这里一切都很美好,直到你开始做更复杂的事

  • 跨模块共享状态 + 联动逻辑
  • 动态模块加载 / 清除(例如用户退出登录后要清空 store)
  • 服务端预注入数据 SSR
  • 多实例 store(例如 tab 页中的独立状态)

Pinia 的那套简洁 API,突然变得不够用了,而你开始手写一堆「自己想不到的 hack」。


动态注册?对不起,Pinia 不支持模块热插拔

Vuex 模块可以 registerModule / unregisterModule,你可以根据场景动态挂载模块:

arduino 复制代码
store.registerModule('user', userModule)

比如后台管理系统中,某些页面权限不同,挂载不同 store 模块是一种必要手段。

而在 Pinia 中?模块一旦定义就永久注册,没有取消注册的 API。

这意味着:

  • 退出登录后你清不了用户模块,得手动 reset()(还不能漏)
  • tab 页中有多个用户表单时,状态会互相污染,无法多实例隔离
  • 想动态挂载模块,自己实现生命周期管理、状态清除、命名空间管理...

你看似在用现代化 API,实则在为它造一个你原本不需要造的轮子


跨模块依赖 + actions 调用,完全无管理

Vuex 中你可以:

scss 复制代码
dispatch('moduleA/doSomething')

在 Pinia 中?你只能:

scss 复制代码
const a = useModuleA()
a.doSomething()

这没有命名空间、没有 action 显式调用路径、没有中间层统一 dispatch,调用链在大型项目中完全不可控

最终你得到的是:

  • 不清楚某个行为被哪些模块调用
  • 不知道模块调用是否存在循环依赖
  • 不好加日志、埋点、权限校验、接口 mock

组件间的调用关系被分散到了文件内部,调试时你面对的不是"一个中心化的 store 流程",而是一堆互相引用的 setup 函数。


SSR 是个伪支持:真正复杂的服务端注入场景会爆炸

Pinia 声称"支持 SSR"。但这支持是浅层的。

你试试下面这个场景:

  • SSR 服务端拉取完用户信息,注入 store 中
  • 客户端 hydration 要同步这些状态
  • 多个模块状态之间有依赖,还得监听响应式变化

你需要用 pinia.state.value 做手动注入,还得处理每个 store 的独立实例,完全没有官方指导,也不封装中间件

而在 Vuex 中,至少你可以通过 store.replaceState() 直接更新所有状态,它生来就是中心化的设计

Pinia 到现在都没有一个像 Vuex 的 plugin / middleware 一样的机制,它并不适合复杂业务和团队协作。


最后压死骆驼的稻草:store 的封装性极差

你做中大型项目,一定会写很多"带状态的业务逻辑":

  • 弹窗的打开关闭 + 表单数据
  • 分页表格的查询条件 + loading 状态 + 总条数
  • 页面缓存数据,用于页面跳转回来后还原

这些在 Vuex 中,你可以写一个 namespaced module,定义状态、mutations、actions,很清晰。

而在 Pinia 中?你得这么做:

javascript 复制代码
const useSearchStore = defineStore('search', {
  state: () => ({
    keyword: '',
    currentPage: 1,
    pageSize: 20,
    list: []
  }),
  actions: {
    async fetchList() {
      // ...
    }
  }
})

然后组件用 useSearchStore。问题是:

  • 这些状态是全局的!你不能开两个页面用两个 store 实例
  • 模块是单例,状态复用完全靠你手写 reset、clear 之类的方法
  • 想自动恢复状态?想要模块有生命周期?你自己写吧

最终你会发现:Pinia 根本不适合处理非全局性状态,但我们大多数页面状态,恰恰是局部的。


所以我回到了 Vuex

是的,我删掉了 pinia,重新把项目切回了 Vuex。

回去的理由其实很简单:

  • Vuex 是中心化的,有结构,有规范,有治理手段
  • 支持动态模块,适合按需加载、缓存管理、页面还原
  • 插件机制健全,做埋点、权限、日志都能统一处理
  • 多人协作时,有命名空间,函数调用清晰

别误会,我不是说 Vuex 完美无缺。

我只是意识到,Pinia 解决的是 Vue2 setup 写法的痛点,不是业务复杂度的痛点。


总结:Pinia 很酷,但它不是状态管理的答案

如果你项目是:

  • 很小的后台项目
  • 状态非常简单
  • 不考虑多模块、用户权限、模块热切换、数据注入

那么 Pinia 当然可以用,甚至用组合式函数都能代替。

但如果你是:

  • 多 tab 页面
  • 状态要可挂载、可销毁
  • 有登录登出、缓存还原、行为跟踪

那请远离 Pinia。

因为你迟早会写出一套比 Vuex 还复杂的补丁系统,只为弥补它的"不支持"。

相关推荐
lexiangqicheng7 分钟前
es6+和css3新增的特性有哪些
前端·es6·css3
拉不动的猪1 小时前
都25年啦,还有谁分不清双向绑定原理,响应式原理、v-model实现原理
前端·javascript·vue.js
烛阴1 小时前
Python枚举类Enum超详细入门与进阶全攻略
前端·python
孟孟~1 小时前
npm run dev 报错:Error: error:0308010C:digital envelope routines::unsupported
前端·npm·node.js
孟孟~1 小时前
npm install 报错:npm error: ...node_modules\deasync npm error command failed
前端·npm·node.js
狂炫一碗大米饭1 小时前
一文打通TypeScript 泛型
前端·javascript·typescript
wh_xia_jun1 小时前
在 Spring Boot 中使用 JSP
java·前端·spring boot
二十雨辰2 小时前
[HTML5]快速掌握canvas
前端·html
tingkeiii2 小时前
【react+antd+vite】优雅的引入svg和阿里巴巴图标
前端·react.js·前端框架
清幽竹客2 小时前
vue-18(使用 Vuex 插件实现高级功能)
前端·vue.js·前端框架·vue