本文最后更新于:2 个月前
Pinia - Vue 全局状态管理的一种新方案
写在前面
如果你有学习过 Vuex 这个全局状态管理的插件,那么你肯定对他的使用方法有过很大的困惑,笔者当初学习使用 Vuex 的时候跟着视频一行行地敲代码,到完全熟练使用花了不少的事件,而且在这其中被其各种冗余的代码使用感到很头痛。
为了解决这些问题,vue 官方的一位成员提出了下一代 vuex 要实现的目标,Github 截图如下:

PR 源地址:https://github.com/vuejs/rfcs/pull/271
简单提取要素就是:
- 同时支持选项式 API 和组合式 API
- 去除 mutations
- 取消模块嵌套结构,保留 store 功能
- Typescript 完全支持
- 自动代码分割
而 Pinia 则在今年(2022)被正式转为官方推荐使用的全局状态管理方案,在 vue3 的官网中的生态系统分类下的官方库可以看到他的身影
Pinia 官方文档:https://pinia.web3doc.top
区别与优势
代码展示
我们用一个例子来看看 Vuex 使用时的代码量:
在状态管理中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import { createStore } from 'vuex'
import sleep from '@/utils/sleep'
const store = createStore({ state () { return { count: 0 } }, getters: { countGreaterThanZero(state) { return state.count > 0 } }, mutations: { increment (state) { state.count++ } }, actions: { async incrementAsync (context) { await sleep(1000) context.commit('increment') } } })
export default store
|
在组件中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <template> <div>{{ count }}</div> </template> <script> import { mapState, mapGetters } from 'vuex'
export default { // 当前组件数据 // ... computed: { // 获取原生状态 ...mapState({ // 箭头函数可使代码更简练 count: state => state.count, }), // 获取派生状态 ...mapGetters({ 'countGreaterThanZero', }) }, methods: { // 变化行为 increment() { this.$store.commit('increment') }, // 异步行为 incrementAsync() { this.$store.dispatch('incrementAsync') } } } </script>
|
在上述的例子中,分别有两个状态获取的模式和两个行为模式,但是我个人在实际开发体验中顶多是直接使用 state
和 mutations
,部分异步的就使用 action
,个人觉得最麻烦的还是需要通过 mapState
进行状态的获取,这样子使得代码的可读性变得非常差,体验大幅度下降。
现在再让我们看看 Pinia 实现以上的功能要怎么做
对一类状态定义的 store:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { defineStore } from 'pinia'
import sleep from '@/utils/sleep'
export const useStore = defineStore('count', { state: () => ({ count: 0 }), actions: { increment() { this.count++ }, async incrementAsync() { await sleep(1000) this.count++ } } })
|
组件中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <div>{{ store.count }}</div> </template> <script setup> import { useStore } from '@/stores/counter'
const store = useStore()
onMounted(() => { // 直接调用 store.increment() store.incrementAsync() }) </script>
|
优势总结
总结一下体验的区别:
- 代码量少了很多,而且与 vue3 的组合式 api 结合的更好,使用起来更方便。
- 对 Typescript 的支持也是非常完善的,使用后都会有自动补全。

- 每个 store 直接独立,可以分开导用,组件中可以直接在 setup 函数下进行取用
- 去除了 mutations, actions 支持同步和异步,不需要分别定义行为
其他体验
扩展 Pinia
官方文档说明:https://pinia.web3doc.top/core-concepts/plugins.html
这个部分笔者并没有实际体验,只是单纯的在文档上进行(纸上谈兵)了解
大概看了一下,pinia 的插件都是以函数的形式安装到 pinia 本身,然后函数本身可以获得操作 pinia 的上下文:
1 2 3 4 5 6 7
| export function myPiniaPlugin(context) { context.pinia context.app context.store context.options }
|
说几个个人认为比较惊喜的点:
添加新的外部属性
我觉得这一点是个好东西,有时候我们可以通过挂载一些全局 store 专用方法和常量来对各种环境进行区分,而不需要从常量文件夹中单独引入使得代码耦合性上升
官方示例是把路由对象添加到其中:
1 2 3 4 5 6 7
| import { markRaw } from 'vue'
import { router } from './router'
pinia.use(({ store }) => { store.router = markRaw(router) })
|
插件内使用 $subscribe $onAction
这个是监听状态改变和 action 执行的钩子,这个让我联想到了 axios 的请求拦截器和响应拦截器,我觉得在这里他也可以发挥相同的功能,比如 全局提示消息
等功能
1 2 3 4 5 6 7 8
| pinia.use(({ store }) => { store.$subscribe(() => { }) store.$onAction(() => { }) })
|
还有更多的用法可以前往官网查看和了解。
Nuxt.js 的服务端渲染
在 Pinia 中服务端渲染只需要在 vite 的配置下进行简单配置即可像 vue 一样正常使用 store,
而原来的 vuex 需要将全局状态区分为客户端与服务端地状态,
1 2 3 4 5 6 7
| export default { buildModules: [ '@pinia/nuxt', ], }
|
总结
Pinia 相比 vuex,省去了许多复杂冗余的代码,让代码的可读性变得更好;通过组合式的 API 使得使用更加灵活,可以更加方便地区分业务逻辑,提升开发效率,相信在未来使用 Pinia 将会成为 vue 全局状态管理地主流,为广大开发者提供一个更好的全局状态解决方案。