一. 概述
在开发Vue项目时,我们一般使用Vuex来进行状态管理,但是在使用Vuex时始终伴随着一些痛点。比如:需要使用Provide/Inject来定义类型化的InjectionKey以便支持TypeScript,模块的结构嵌套、命名空间以及对新手比较难理解的流程规范等。Pinia的出现很好的解决了这些痛点。本质上Pinia也是Vuex团队核心成员开发的,在Vuex的基础上提出了一些改进。与Vuex相比,Pinia去除了Vuex中对于同步函数Mutations和异步函数Actions的区分。并且实现了在Vuex5中想要的大部分内容。
二.使用
在介绍Pinia之前我们先来回顾一下Vuex的使用流程
1.Vuex
Vuex是一个专为Vue.js应用程序开发的状态管理库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。它主要用来解决多个组件状态共享的问题。

主流程: 在Store中创建要共享的状态state,修改state流程:
Vue Compontentsdispatch Actions(在Actions中定义异步函数),Action commit Mutations,在Mutations中我们定义直接修改state的纯函数,state修改促使Vue compontents 做响应式变化。
(1) 核心概念
State: 就是组件所依赖的状态对象。我们可以在里面定义我们组件所依赖的数据。可以在Vue组件中通过this.$store.state.xxx获取state里面的数据.Getter:从store中的state派生出的一些状态,可以把他理解为是store的计算属性.Mutation:更改store中状态的唯一方法是提交 mutation,我们通过在mutation中定义方法来改变state里面的数据.
在Vue组件中,我们通过
store.commit('方法名'),来提交mutation。需要注意的是,Mutation 必须是同步函数。
Action:action类似于 mutation,不同在于:
Action 提交的是 mutation,而不是直接变更状态.
Action 可以包含任意异步操作.
Module: 当我们的应用较大时,为了避免所有状态会集中到一个比较大的对象中,Vuex允许我们将 store 分割成模块(module),你可以把它理解为Redux中的combineReducer的作用.
(2) 在组合式API中对TypeScript的支持
在使用组合式API编写Vue组件时候,我们希望使用useStore返回类型化的store,流程大概如下:
- 定义类型化的
InjectionKey。 - 将
store安装到Vue应用时提供类型化的InjectionKey。 - 将类型化的
InjectionKey传给useStore方法并简化useStore用法
1 | // store.ts |
main.ts
1 | import { createApp } from 'vue' |
组件中使用
1 | <script lang="ts"> |
Pinia 的使用

基本特点
Pinia同样是一个Vue的状态管理工具,在Vuex的基础上提出了一些改进。与vuex相比,Pinia 最大的特点是:简便。
- 它没有
mutation,他只有state,getters,action,在action中支持同步与异步方法来修改state数据 - 类型安全,与
TypeScript一起使用时具有可靠的类型推断支持 - 模块化设计,通过构建多个存储模块,可以让程序自动拆分它们。
- 非常轻巧,只有大约 1kb 的大小。
- 不再有
modules的嵌套结构,没有命名空间模块 Pinia支持扩展,可以非常方便地通过本地存储,事物等进行扩展。- 支持服务器端渲染
安装与使用
安装
1 | yarn add pinia |
核心概念:
store: 使用defineStore()函数定义一个store,第一个参数是应用程序中store的唯一id. 里面包含state、getters 和 actions, 与Vuex相比没有了Mutations.
1 | export const useStore = defineStore('main', { |
注意:store 是一个用reactive 包裹的对象,这意味着不需要在getter 之后写.value,但是,就像setup 中的props 一样,我们不能对其进行解构.
1 | export default defineComponent({ |
当然你可以使用computed来响应式的获取state的值(这与Vuex中需要创建computed引用以保留其响应性类似),但是我们通常的做法是使用storeToRefs响应式解构Store.
1 | const store = useStore() |
State: 在Pinia中,状态被定义为返回初始状态的函数.
1 | import { defineStore } from 'pinia' |
组件中state的获取与修改:
在Vuex中我们修改state的值必须在mutation中定义方法进行修改,而在pinia中我们有多中修改state的方式.
- 基本方法:
1
2const store = useStore()
store.counter++ - 重置状态:
1
2const store = useStore()
store.$reset() - 使用
$patch修改state
[1] 使用部分state对象进行修改
1 | const mainStore = useMainStore() |
[2] $patch方法也可以接受一个函数来批量修改集合内部分对象的值
1 | cartStore.$patch((state) => { |
替换state
可以通过将其 $state 属性设置为新对象,来替换Store的整个状态:1
mainStore.$state = { name: '', counter: 0 }
访问其他模块的
stateVuex中我们要访问其他带命名空间的模块的state我们需要使用rootState
1
2
3
4
5
6
7
8addAsyncTabs ({ state, commit, rootState, rootGetters }:ActionContext<TabsState, RootState>, tab:ITab): void {
/// 通过rootState 访问main的数据
console.log('rootState.main.count=======', rootState.main.count)
if (state.tabLists.some(item => item.id === tab.id)) { return }
setTimeout(() => {
state.tabLists.push(tab)
}, 1000)
},Pinia 中访问其他
store的state1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import { useInputStore } from './inputStore'
export const useListStore = defineStore('listStore', {
state: () => {
return {
itemList: [] as IItemDate[],
counter: 0
}
},
getters: {
},
actions: {
addList (item: IItemDate) {
this.itemList.push(item)
///获取store,直接调用
const inputStore = useInputStore()
inputStore.inputValue = ''
}
})
Getter: Getter完全等同于Store状态的计算值.
1 | export const useStore = defineStore('main', { |
如果需要使用
this访问到 整个store的实例,在TypeScript需要定义返回类型.
在setup()中使用:
1 | export default { |
访问其他模块的getter
对于
Vuex而言如果要访问其他命名空间模块的getter,需要使用rootGetters属性1
2
3
4
5/// action 方法
addAsyncTabs ({ state, commit, rootState, rootGetters }:ActionContext<TabsState, RootState>, tab:ITab): void {
/// 通过rootGetters 访问main的数据
console.log('rootGetters[]=======', rootGetters['main/getCount'])
}Pinia中访问其他store中的getter1
2
3
4
5
6
7
8
9
10
11
12
13import { useOtherStore } from './other-store'
export const useStore = defineStore('main', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})
Action:actions 相当于组件中的methods,使用defineStore()中的 actions 属性定义.
1 | export const useStore = defineStore('main', { |
pinia中没有mutation属性,我们可以在action中定义业务逻辑,action可以是异步的,可以在其中await 任何 API调用甚至其他操作.
1 | ... |
访问其他store中的Action
要使用另一个 store中的action ,可以直接在操作内部使用它:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import { useAuthStore } from './auth-store'
export const useSettingsStore = defineStore('settings', {
state: () => ({
// ...
}),
actions: {
async fetchUserPreferences(preferences) {
const auth = useAuthStore()
///调用其他store的action
if (auth.isAuthenticated()) {
this.preferences = await fetchPreferences()
} else {
throw new Error('User must be authenticated')
}
},
},
})在
Vuex中如果要调用另一个模块的Action,我们需要在当前模块中注册该方法为全局的Action,1
2
3
4
5
6
7/// 注册全局Action
globalSetCount: {
root: true,/// 设置root 为true
handler ({ commit }:ActionContext<MainState, RootState>, count:number):void {
commit('setCount', count)
}
}在另一个模块中对其进行
dispatch调用1
2
3
4/// 调用全局命名空间的函数
handelGlobalAction ({ dispatch }:ActionContext<TabsState, RootState>):void {
dispatch('globalSetCount', 100, { root: true })
}
三. 总结
与 Vuex 相比,Pinia 提供了一个更简单的 API,具有更少的操作,提供Composition API,最重要的是,在与TypeScript一起使用时具有可靠的类型推断支持,如果你正在开发一个新项目并且使用了TypeScript,可以尝试一下pinia,相信不会让你失望。
一些参考: