Vue3开发到底选Pinia还是Vuex?最新版踩坑经验分享
最近半年在处理几个中大型Vue3电商平台和工具类项目的状态管理迁移,也踩了不少新旧工具混用的坑,后台收到了好多同行的私信,都是围绕“Vue3出来这么久了,我到底该坚守Vuex还是换Pinia?”“Pinia最新有没有什么大问题?”“Vuex4能不能适配Vue3完全体?”这类问题,今天就整理成一套完整的问答,把我这段时间的测试数据、项目实际踩坑、官方文档的更新点全揉进去说,尽量给大家一个不纠结的答案。
先搞清楚:Vuex和Pinia到底有什么血缘关系?
别一上来就对比哪个快哪个小,先理明白它们的背景,能帮你理解为什么官方现在主推Pinia,最早Vuex3是为Vue2服务的,设计思路偏保守,解决的是“多个组件共享复杂数据不混乱”的核心问题,但结构比较重——要分state、getters、mutations、actions,actions还必须是异步修改state的唯一入口,mutations只能同步,而且有严格的写法规范,大项目里改个状态要转三层手,确实麻烦。
后来尤雨溪在2021年左右做Vue3生态重构的时候,搞了个“轻量级Vuex实验版”叫Pinia,没错,就是实验出来转正的亲儿子,当时做实验的目的就是去掉Vuex的冗余结构,让状态管理写起来像写Vue3组件一样爽,现在Vue3官方文档里的状态管理部分,已经把Pinia放在第一推荐位了,Vuex4只是作为“兼容老Vue3过渡项目”的备选方案存在。
新手入门+小项目练手,直接闭眼冲Pinia,别犹豫
为什么这么绝对?先给大家看两段实现同一个计数器功能的代码对比,看完你就懂了。
Vuex4的计数器写法
// store/index.js
import { createStore } from 'vuex'
export default createStore({
state: {
count: 0
},
getters: {
doubleCount: state => state.count * 2
},
mutations: {
increment(state) {
state.count++
},
setCount(state, payload) {
state.count = payload
}
},
actions: {
async incrementAsync({ commit }) {
await new Promise(resolve => setTimeout(resolve, 1000))
commit('increment')
}
}
})
<!-- 使用的组件 -->
<script setup>
import { useStore } from 'vuex'
const store = useStore()
const handleIncrement = () => store.commit('increment')
const handleAsync = () => store.dispatch('incrementAsync')
const handleSet = (val) => store.commit('setCount', val)
</script>
Pinia的计数器写法
// store/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
},
async incrementAsync() {
await new Promise(resolve => setTimeout(resolve, 1000))
this.count++
}
}
})
<!-- 使用的组件 -->
<script setup>
import { useCounterStore } from '@/store/counter'
const counterStore = useCounterStore()
const handleIncrement = () => counterStore.increment()
const handleAsync = () => counterStore.incrementAsync()
const handleSet = (val) => counterStore.count = val // 直接修改,爽不爽?
</script>
对比下来最直观的三点不同:
- 结构简化到极致:去掉了mutations这个中间层,不管是同步还是异步修改,都直接写在actions里(Pinia里actions不用区分同步异步,名字叫习惯就行),或者像组件data一样直接赋值修改,新手完全不会被“mutations不能写异步,actions不能直接改state”这种规则绕晕。
- 模块化更灵活,没有命名空间烦恼:Pinia每个store都是独立的defineStore实例,默认带命名空间,不用像Vuex那样每个模块都要写
namespaced: true,组件里使用的时候也不用加前缀,直接调用对应store的方法就行,拆分模块像拆分组件一样简单,甚至可以把不同功能的store放在不同的文件夹里,不用全挤在一个index.js。 - 对TypeScript的支持碾压Vuex4:这一点对做中大型项目的朋友来说太重要了,Vuex4虽然官方说支持TS,但写起来要写一堆泛型、接口,代码量翻好几倍,而且智能提示经常抽风;Pinia从出生就是为TS设计的,不用写额外的接口定义,state、getters、actions的智能提示自动就有,类型检查也严格,大大减少了开发时的低级错误。
我去年年底帮一个朋友做的Vue3+TS外卖点单小程序练手项目,一开始用了Vuex4,写了一周就改Pinia了,光是状态管理的代码就少了三分之一,智能提示和类型检查也让他这个刚学TS的新手少踩了好多“拼写错误、传参类型不对”的坑,开发效率至少提升了40%。
那老项目怎么办?Vue3完全体+TS的老Vuex4项目要不要迁移?
这个问题得分情况讨论,不能一概而论。
Vue3项目还在用Vuex4+JS,没有用TS
这种项目如果规模不大(比如状态模块不超过5个),或者已经上线了、没有新增太多复杂状态管理需求的话,完全可以暂时不迁移,继续用Vuex4就好,毕竟官方还在维护Vuex4,最近一次更新是2024年2月,修复了一些小bug,但如果项目要长期迭代,或者后续要加复杂的权限管理、购物车、主题切换这类共享状态多的功能,建议趁迭代间隙慢慢迁移,比如先把新增的功能模块用Pinia写,旧的Vuex4模块留着,Pinia和Vuex4是可以共存的,完全不会冲突。
Vue3完全体+TS的中大型老项目,已经用了Vuex4
这种情况就比较纠结了,我最近刚接手的一个电商后台管理系统就是这样——上线一年多,状态模块有12个,全是Vuex4+TS写的,一开始我也想全部推翻重写,但评估下来时间成本太高(至少要花两周时间,还要测试所有模块),最后选择了“渐进式迁移”的方案:
- 先在项目里安装Pinia,和Vuex4共存;
- 把新开发的报表统计、物流追踪模块用Pinia写,感受一下Pinia的开发体验;
- 趁每个旧模块的bug修复或功能优化时,把对应的Vuex4模块改成Pinia;
- 最后把剩下的通用模块(比如用户信息、权限管理)最后一起迁移。
整个过程花了大概一个月的时间,没有影响项目的正常迭代,而且迁移后的模块代码量减少了,智能提示更流畅,测试的时候也发现了几个之前TS没检查出来的隐藏bug(比如之前Vuex4里传的payload类型定义错了,但实际运行时没报错,因为JS是弱类型,TS写的Vuex4提示抽风也没发现,改成Pinia后直接提示类型错误,马上就改了)。
有没有必要一次性全部重写?
除非是项目重构,否则绝对不建议,一次性重写的时间成本太高,而且容易引入新的bug,得不偿失,渐进式迁移是最稳妥的方案。
很多人担心Pinia太“轻”,hold不住中大型项目?最新版Pinia已经解决了这个问题
之前Pinia刚转正的时候,确实有一些问题:比如没有内置的状态持久化插件、没有devtools的时间旅行调试(这个功能后来加了)、模块之间的通信不如Vuex4方便?不对,Pinia模块之间的通信其实更简单,直接在一个store里引入另一个store的useXxxStore就行,和组件里使用一样。
现在最新版的Pinia(截至2024年6月是2.1.7版本),已经非常成熟了,有很多官方和第三方的插件可以用,完全可以hold住中大型项目:
-
内置了devtools支持,时间旅行调试功能丝滑:这个功能和Vuex4的devtools一模一样,可以查看每一次状态的变化,还可以回滚到任意状态,调试超级方便;
-
状态持久化插件超级多,比如pinia-plugin-persistedstate:这个插件是目前最火的Pinia持久化插件,支持localStorage、sessionStorage、cookie、甚至是自定义存储方式,配置起来也超级简单,只要在defineStore里加个persist配置就行,不用像Vuex4那样自己写storage相关的代码;
-
有官方的组合式API写法选项:如果你更喜欢Vue3组合式API的写法,Pinia也支持,不用像选项式API那样分state、getters、actions,直接像写setup函数一样写就行,
import { defineStore } from 'pinia' import { ref, computed } from 'vue' export const useCounterStore = defineStore('counter', () => { const count = ref(0) const doubleCount = computed(() => count.value * 2) function increment() { count.value++ } async function incrementAsync() { await new Promise(resolve => setTimeout(resolve, 1000)) count.value++ } return { count, doubleCount, increment, incrementAsync } })这种写法和写Vue3组件的setup函数完全一致,喜欢组合式API的朋友肯定会爱上它;
-
有完善的文档和社区支持:Pinia的官方文档非常详细,而且有中文版本,社区里的问题解答也很多,遇到问题基本上百度一下就能找到答案。
我接手的那个电商后台管理系统,迁移到Pinia之后,用了pinia-plugin-persistedstate做用户信息和权限管理的持久化,用了组合式API写法写了一些复杂的报表统计模块,用了devtools的时间旅行调试功能解决了几个状态混乱的问题,现在运行得非常稳定,开发效率也比之前高了很多。
Pinia最新版有没有什么需要注意的踩坑点?
这段时间用下来,我也踩了几个Pinia最新版的小坑,给大家提个醒:
- 直接修改state的时候,devtools里的时间旅行调试功能有时候会失效:这不是bug,是Pinia的设计思路——如果直接修改state(比如
counterStore.count = 10),Pinia会把这次修改标记为“直接修改”,不会出现在时间旅行调试的记录里;如果要让直接修改也出现在时间旅行调试的记录里,可以用$patch方法,// 单个属性修改 counterStore.$patch({ count: 10 }) // 多个属性修改,或者需要写逻辑 counterStore.$patch((state) => { state.count++ state.doubleCount = state.count * 2 // 其实不用,getters会自动更新 })或者直接写在actions里,不管是同步还是异步,actions里的修改都会出现在时间旅行调试的记录里。
- 组合式API写法的store,return的时候不要忘了返回所有需要在组件里使用的state、getters、actions:这个是低级错误,但很多新手(包括我刚开始用的时候)都会犯,忘了return的话,组件里就用不了对应的属性和方法;
- pinia-plugin-persistedstate插件的默认存储方式是localStorage,如果存储的数据比较大,可能会影响浏览器的性能:如果存储的数据比较大(比如超过1MB),建议用sessionStorage或者自定义存储方式,比如IndexedDB;
- Pinia的store是单例模式吗? 是的,每个defineStore实例在整个应用里都是单例的,不管你在多少个组件里调用useXxxStore,得到的都是同一个实例,所以不用担心状态不同步的问题;
- SSR项目里使用Pinia需要注意什么? SSR项目里使用Pinia,不能直接在组件的setup函数外面调用useXxxStore,否则会导致状态污染,必须在组件的setup函数里面,或者在onServerPrefetch钩子里面调用,官方文档里有详细的SSR配置说明,大家可以去看一下。
最后总结一下:Vue3状态管理到底选Pinia还是Vuex?
给大家一个非常清晰的决策树:
- 如果是新手入门+小项目练手:直接闭眼冲Pinia;
- 如果是Vue3新开发的项目(不管大小):直接闭眼冲Pinia;
- 如果是Vue3+JS的老项目,规模小、没有新增复杂状态需求:暂时用Vuex4,后续迭代间隙慢慢迁移;
- 如果是Vue3+JS/TS的老项目,规模大、有新增复杂状态需求:渐进式迁移到Pinia;
- 如果是必须兼容IE11的Vue3项目:哦,不对,Vue3本身就不兼容IE11,所以这种情况不存在。
Pinia是目前Vue3生态里最好用的状态管理工具,没有之一,它的结构简单、对TS支持好、社区活跃、插件丰富,完全可以替代Vuex4成为Vue3状态管理的首选,如果你还在纠结,不如先花10分钟写个小demo试试Pinia,相信你会爱上它的。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


