Code前端首页关于Code前端联系我们

Vue3开发到底选Pinia还是Vuex?最新版踩坑经验分享

terry 1小时前 阅读数 16 #Vue
文章标签 PiniaVuex

最近半年在处理几个中大型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>

对比下来最直观的三点不同:

  1. 结构简化到极致:去掉了mutations这个中间层,不管是同步还是异步修改,都直接写在actions里(Pinia里actions不用区分同步异步,名字叫习惯就行),或者像组件data一样直接赋值修改,新手完全不会被“mutations不能写异步,actions不能直接改state”这种规则绕晕。
  2. 模块化更灵活,没有命名空间烦恼:Pinia每个store都是独立的defineStore实例,默认带命名空间,不用像Vuex那样每个模块都要写namespaced: true,组件里使用的时候也不用加前缀,直接调用对应store的方法就行,拆分模块像拆分组件一样简单,甚至可以把不同功能的store放在不同的文件夹里,不用全挤在一个index.js。
  3. 对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写的,一开始我也想全部推翻重写,但评估下来时间成本太高(至少要花两周时间,还要测试所有模块),最后选择了“渐进式迁移”的方案:

  1. 先在项目里安装Pinia,和Vuex4共存;
  2. 把新开发的报表统计、物流追踪模块用Pinia写,感受一下Pinia的开发体验;
  3. 趁每个旧模块的bug修复或功能优化时,把对应的Vuex4模块改成Pinia;
  4. 最后把剩下的通用模块(比如用户信息、权限管理)最后一起迁移。

整个过程花了大概一个月的时间,没有影响项目的正常迭代,而且迁移后的模块代码量减少了,智能提示更流畅,测试的时候也发现了几个之前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住中大型项目:

  1. 内置了devtools支持,时间旅行调试功能丝滑:这个功能和Vuex4的devtools一模一样,可以查看每一次状态的变化,还可以回滚到任意状态,调试超级方便;

  2. 状态持久化插件超级多,比如pinia-plugin-persistedstate:这个插件是目前最火的Pinia持久化插件,支持localStorage、sessionStorage、cookie、甚至是自定义存储方式,配置起来也超级简单,只要在defineStore里加个persist配置就行,不用像Vuex4那样自己写storage相关的代码;

  3. 有官方的组合式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的朋友肯定会爱上它;

  4. 有完善的文档和社区支持:Pinia的官方文档非常详细,而且有中文版本,社区里的问题解答也很多,遇到问题基本上百度一下就能找到答案。

我接手的那个电商后台管理系统,迁移到Pinia之后,用了pinia-plugin-persistedstate做用户信息和权限管理的持久化,用了组合式API写法写了一些复杂的报表统计模块,用了devtools的时间旅行调试功能解决了几个状态混乱的问题,现在运行得非常稳定,开发效率也比之前高了很多。

Pinia最新版有没有什么需要注意的踩坑点?

这段时间用下来,我也踩了几个Pinia最新版的小坑,给大家提个醒:

  1. 直接修改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里的修改都会出现在时间旅行调试的记录里。

  2. 组合式API写法的store,return的时候不要忘了返回所有需要在组件里使用的state、getters、actions:这个是低级错误,但很多新手(包括我刚开始用的时候)都会犯,忘了return的话,组件里就用不了对应的属性和方法;
  3. pinia-plugin-persistedstate插件的默认存储方式是localStorage,如果存储的数据比较大,可能会影响浏览器的性能:如果存储的数据比较大(比如超过1MB),建议用sessionStorage或者自定义存储方式,比如IndexedDB;
  4. Pinia的store是单例模式吗? 是的,每个defineStore实例在整个应用里都是单例的,不管你在多少个组件里调用useXxxStore,得到的都是同一个实例,所以不用担心状态不同步的问题;
  5. SSR项目里使用Pinia需要注意什么? SSR项目里使用Pinia,不能直接在组件的setup函数外面调用useXxxStore,否则会导致状态污染,必须在组件的setup函数里面,或者在onServerPrefetch钩子里面调用,官方文档里有详细的SSR配置说明,大家可以去看一下。

最后总结一下:Vue3状态管理到底选Pinia还是Vuex?

给大家一个非常清晰的决策树:

  1. 如果是新手入门+小项目练手:直接闭眼冲Pinia;
  2. 如果是Vue3新开发的项目(不管大小):直接闭眼冲Pinia;
  3. 如果是Vue3+JS的老项目,规模小、没有新增复杂状态需求:暂时用Vuex4,后续迭代间隙慢慢迁移;
  4. 如果是Vue3+JS/TS的老项目,规模大、有新增复杂状态需求:渐进式迁移到Pinia;
  5. 如果是必须兼容IE11的Vue3项目:哦,不对,Vue3本身就不兼容IE11,所以这种情况不存在。

Pinia是目前Vue3生态里最好用的状态管理工具,没有之一,它的结构简单、对TS支持好、社区活跃、插件丰富,完全可以替代Vuex4成为Vue3状态管理的首选,如果你还在纠结,不如先花10分钟写个小demo试试Pinia,相信你会爱上它的。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门