用Vue3做项目时选Pinia还是Vuex?具体怎么用才顺手?
接触Vue3做项目的时候,很多人会在选状态管理工具这里卡壳——到底是接着用熟的Vuex4,还是直接上手官方推荐的Pinia?今天就用大白话把这个事儿掰扯清楚,再把Pinia的核心用法、避坑点也一起唠明白。
为什么现在做Vue3官方更推Pinia?
之前Vuex4虽然能适配Vue3的Composition API,但本质上还是延续的Options API时代的逻辑,比如模块划分太死、mutations只能传单参数、不能直接调试看状态更新路径这些老问题,一个都没解决,而Pinia呢,从一开始就是为Vue3量身定做的,连名字据说还是为了纪念菠萝这种「果实聚在一起的形态——这不就是我们想要的“轻量级、易组合的状态管理工具嘛。
对比几个硬伤来看,选Pinia的理由真的太明显了,首先是代码量少,写起来爽,Vuex要写state、getters、mutations、actions四个核心属性,Pinia直接把mutations取消了,state直接修改就行?哦不对,是用方法就行?别慌,后面具体说,其次是没有命名空间冲突的烦恼,Vuex模块多了,dispatch或者commit的时候,长串的命名空间嵌套太容易写错,Pinia每个store都是独立的小模块,想调用直接用store名.变量或者store名.方法,清爽得很,还有就是TS支持完美,之前Vuex写TS类型提示的时候,要么要写好多接口,要么用mapStateToProps/mapMutations这种绕死人的辅助函数转,体验一言难尽,Pinia在TS里直接自动推导,变量和方法的类型都能精准出来,再也不用担心打错字或者传参错了IDE半天找不到问题,调试这块,Vue DevTools对Pinia的支持也更友好,能直接看到每个store的修改记录,还能撤销重做,状态管理的安全感拉满。
还有一个更实在的点是,现在官方的Vue3脚手架、Vite模板,默认推荐的状态管理都是Pinia,不管是新手入门还是找资料还是查问题,资源比Vuex4多太多了,踩坑也容易找到解决方案,除非你之前的老项目还在用Vuex2/3,升级到Vue3不想改太多东西才用Vuex4,不然真的别犹豫,直接Pinia。
Pinia的核心三步曲是什么?怎么从零搭建?
别听那些说Pinia难的,其实核心就三步:安装配置、定义store、使用store,连小孩搭积木一样简单。
第一步:安装配置
不管是用Vite还是Vue CLI,安装命令都差不多,我一般用npm的话,就是npm install pinia,用yarn就是yarn add pinia,pnpm更快,pnpm add pinia就行,安装好之后,要在Vue3的入口文件main.js或者main.ts里注册一下,怎么注册呢?先从pinia里引createPinia,然后用app.use(createPinia()),就这么一行,比Vuex还简单。
第二步:定义store
这是最关键的一步,Pinia定义store有两种方式,一种是Options Store,一种是Setup Store,官方现在更推Setup Store,因为和Vue3的Composition API完全一致,写起来也更顺,更方便复用。
先说说Setup Store的写法,首先要从pinia里引defineStore,然后给store起个唯一的名字,比如叫useCounterStore,这个名字就是调试的时候在Vue DevTools里能看到的标识,别和其他store重名就行,然后defineStore的第二个参数,就是一个回调函数,和Vue3的setup函数一样,里面可以写ref、reactive、computed、watch这些Vue3原生的响应式数据和方法,最后return出来就行。
举个最简单的计数器例子,CounterStore.js文件里这么写: 先引defineStore,然后defineStore('counter', () => { // 定义响应式数据,用ref和Vue3的ref就行,因为Setup Store里自动响应式 const count = ref(0) // 计算属性用computed const doubleCount = computed(() => count.value * 2) // 定义方法,不管是同步还是异步,都直接写普通函数就行,不需要像Vuex那样分mutations和actions const increment = () => { count.value++ } const async incrementAsync = async (num = 1) => { // 这里可以加个定时器模拟异步请求 await new Promise(resolve => setTimeout(resolve, 1000)) count.value += num } // 最后一定要return出来要暴露给组件或者其他store用的东西 return { count, doubleCount, increment, incrementAsync } })
Options Store的写法也提一嘴,和Vuex有点像,但更简化了,不用写mutations,直接用actions或者methods(Setup里的方法)改state就行,不过现在大家都用Composition API了,还是学Setup Store更实用。
第三步:使用store
使用store也有两种方式,一种是在组件里直接调用defineStore的返回值,另一种是用辅助函数,不过直接调用返回值更灵活,推荐这个。
在组件里怎么用呢?首先从刚才写的CounterStore.js里引useCounterStore,然后在setup函数或者script setup里直接调用const counterStore = useCounterStore(),然后就可以直接counterStore.count,counterStore.increment(),counterStore.incrementAsync(),counterStore.doubleCount了,连.value都不用写!因为defineStore已经自动解包了ref,这点特别贴心。
如果是Options API的组件怎么办?也可以用辅助函数,比如mapState、mapActions这些,不过用法和Vuex差不多,但我觉得还是Composition API直接调用方便。
Pinia有哪些新手容易踩的坑?怎么避?
虽然Pinia简单,但新手还是容易踩几个小坑,提前知道就不会浪费时间。
第一个坑:忘记return,Setup Store里定义的东西一定要return出来,不然在组件里或者其他store里根本用不了,这点和Vue3的setup函数一模一样,我刚开始写的时候就犯过这个错,找了半天发现没return。
第二个坑:直接修改store里的整个对象属性,不是整个对象替换的话,直接赋值没问题,但如果是整个对象替换的话,别用普通赋值,要用Vue3的响应式怎么弄,这里就怎么弄,比如用Object.assign,或者展开运算符再赋值,不然就直接替换整个对象,比如counterStore.someObj = { ...counterStore.someObj, newProp: 123 },这样才会保持响应式,不过一般直接用reactive定义的对象,直接修改属性就行,比如counterStore.someObj.name = '张三',没问题的。
第三个坑:用解构赋值store之后响应式丢失,如果在组件里直接解构store,比如const { count, increment } = counterStore,这样count的话,count就会变成普通的数字,失去响应式了,那怎么办?这里可以用toRefs,或者直接用counterStore.count,或者用pinia提供的storeToRefs,哦对,storeToRefs更推荐,因为它只会把state里的响应式数据转成ref,方法还是原来的方法,还保持响应式,比如const { count, doubleCount } = storeToRefs(counterStore),const { increment } = counterStore,这样就没问题了。
第四个坑:异步操作的时候,别把异步的逻辑写错,Setup Store里异步操作直接写async/await就行,和普通的Vue3组件里的异步方法一样,不需要像Vuex那样dispatch再commit,这点很方便,但要注意async函数里的错误处理,比如加个try/catch,不然请求失败的话没有提示。
第五个坑:多个store之间怎么互相调用?这个也很简单,直接在一个store的回调函数里,调用另一个store的defineStore返回值就行,比如在UserStore里要调用CounterStore,就先引useCounterStore,然后在UserStore的方法里调用const counterStore = useCounterStore(),然后就可以用counterStore的东西了,要不要注意初始化时机?不用,Pinia会自动处理的,不用担心循环依赖的问题,这点官方做的比Vuex强多了。
Pinia有哪些进阶用法?让你的代码更优雅?
学会了基础用法之后,再学几个进阶用法,代码会写得更顺,更易维护。
第一个进阶用法:持久化store,有时候我们希望页面刷新之后,store里的某些数据还在,比如用户登录状态、购物车数据,这时候就可以用pinia-plugin-persistedstate这个插件,安装配置也很简单,npm install pinia-plugin-persistedstate,然后在main.js里注册pinia的时候,把这个插件加进去就行,然后在定义store的时候,配置一下persist属性,比如在useCounterStore里,return之后,第三个参数是个对象,里面写persist: true,这样刷新页面之后count就不会重置了,如果只想要持久化某些数据,或者换存储方式(比如sessionStorage),也可以配置persist的配置项,比如persist: { storage: sessionStorage, paths: ['count'] },这样就只会持久化count,用sessionStorage存储。
第二个进阶用法:action的拦截器,Pinia的action拦截器可以在action执行之前、之后、出错的时候做一些事情,比如记录日志、loading状态管理,比如在定义store的时候,配置一下useCounterStore的第三个参数对象里,写actions: { increment() { console.log('increment执行前') }, incrementAsync() { console.log('incrementAsync执行前') } },不过这个是Options Store的写法?不对,Setup Store也可以用pinia的useStore.$onAction方法,或者用插件也行,不过我一般用插件的话更全局统一管理更方便,比如在main.js里注册pinia之后,遍历所有的store,然后给每个store的action都加个拦截器。
第三个进阶用法:重置store的state,有时候我们希望重置store里的所有state,比如用户退出登录的时候,清空用户信息,Setup Store里怎么重置呢?可以在定义store的时候,return一个reset方法,把所有的state数据都重置成初始值,比如const reset = () => { count.value = 0, someObj.value = { name: '' },或者用pinia提供的$reset方法?哦对,Options Store里默认有$reset方法,Setup Store里没有,除非自己写一个,那怎么让Setup Store也有$reset方法?可以用插件,或者在定义store的时候,先把初始值定义成一个函数,然后return的时候,把state从初始值函数里取,然后reset方法就调用初始值函数重新赋值就行。 defineStore('counter', () => { const getInitialState = () => ({ count: 0, someObj: { name: '' } }) const state = reactive(getInitialState()) const count = computed(() => state.count) const someObj = computed(() => state.someObj) const reset = () => { Object.assign(state, getInitialState()) } // 其他方法... return { state, count, someObj, reset } })
总结一下
用Vue3做新项目,直接选Pinia就对了,代码量少、TS支持好、调试方便、资源多,核心三步曲:安装配置、定义Setup Store、用storeToRefs避免解构响应式丢失,进阶用法可以学持久化、action拦截器、重置state,多练几个小项目,比如购物车、用户登录、Todo List,很快就能上手了,用Pinia管理状态真的比Vuex爽太多。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



