Pinia新一代Vue状态管理库

简介

随着Vue2.x版本的停止更新维护,Vue3版本的迭代更新,新版本的相关生态也逐渐出现在大家的视野中。

其中,就包括Vue的新一代状态管理库 Pinia。

在Pina之前,我们一个复杂的应用通常是用Vuex来管理状态的,其中Vue2.x的版本对应的Vuex版本是Vuex3.x,而Vue3的版本对应的Vuex版本是Vuex4.x。

Pinia其实最初迭代的版本是Vuex5.x, 主要内容是设计及重构状态管理以适用于 Composition API,其设计原则跟思想与 Vuex 保持一致。所以最后将Pina作为新的推荐方案来代替Vuex。

Tips:

虽然Pina开始是要适用于Composition API,但作者初心也是同时支撑Vue2.x和Vue3.x,所以无论你的Vue是哪个版本都可以愉快的使用Pinia。

为什么要使用Pinia

在Vue3中,我们通常用ref或reactive来定义数据,所以会有同学说,我用如下方法定义并共享全局数据也可以做到状态管理:

1
2
3
export const state = reactive({})
// 或者
export const otherState = ref()

诚然,这种方式在单页面应用也可以实现状态管理,但如果你的应用使用了SSR(服务端渲染,后续会出一期文章单独讲一下),那可能会存在相关的安全问题,此外,Pinia还有其他的优势:

测试工具集

插件:可通过插件扩展 Pinia 功能

为 JS 开发者提供适当的 TypeScript 支持以及自动补全功能

支持服务端渲染

Devtools 支持(超级好用)

  • 追踪 actions、mutations 的时间线
  • 在组件中展示它们所用到的 Store
  • 让调试更容易的 Time travel

热更新

  • 不必重载页面即可修改 Store
  • 开发时可保持当前的 State

Pinia主要构成

  • Store: 用于存储应用的状态,类似于Vuex中的state。
  • Action: 用于业务逻辑,类似于Vuex中的action。
  • Getter: 用于获取应用的状态,类似于Vuex中的getter。
  • Plugins: Pinia 插件。

Pinia与Vuex区别

1、 移除 mutations: 在 Vuex 中,状态的改变必须通过提交 mutations 实现,而 mutations 只能执行同步操作。为了支持异步数据操作,Vuex 引入了 actions,通过 actions 来调用 mutations 修改数据。在 Pinia 中,actions 既支持同步操作也支持异步操作,因此 mutations 显得有些冗余,因而被移除了。这简化了状态管理的逻辑,减少了不必要的复杂性。

2、对 TypeScript 的支持更为友好: 使用 Pinia 时,不再需要定义复杂的封装器来支持 TypeScript。Pinia 直接内置了对 TypeScript 的良好支持,提供了更自然的类型推断和安全性,使得开发体验更加顺畅。

3、避免了魔法字符串: 在使用 Vuex 时,我们经常会看到类似 this.$store.commit(“xxxxx”) 这样的魔法字符串。尽管可以将这些字符串提取为常量进行维护,但这依然不够便捷。在 Pinia 中,这种方式被优化了。我们可以直接调用 defineStore 后暴露出来的方法,通过 $patch、actions 的方式,或是直接修改 store 中的值,这样避免了魔法字符串的使用,提高了代码的可读性和维护性。

4、动态的 stores: Pinia 支持随时定义 stores,它们默认是动态的。这意味着开发者可以在需要时灵活地创建和使用 stores,不再需要像 Vuex 那样繁琐地进行动态添加。

5、简化了模块管理: 在 Pinia 中,不再需要嵌套结构的模块。这与 Vuex 的模块化结构不同,在 Vuex 中我们经常需要嵌套命名空间模块来组织状态。在 Pinia 中,模块化被简化了,我们可以用更简洁的方式来管理应用的状态,省去了复杂的模块嵌套和命名空间的配置。

实际体验

接下来,我们通过一个简单的示例来体验一下Pina。

1、项目搭建

前提:使用vite搭建基础的vue3项目来进行演示。(此步骤不多赘述,可自行查阅资料)。

2、安装pina

1
2
3
4
5
6
7
8
9
10
11
12
//src/main.js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from 'pinia' //引入pina

const pinia = createPinia() //创建pina实例
const app = createApp(App)

app.use(pinia) // 挂载pina
app.mount('#app')

3、创建 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
//src/store/index.js
import { defineStore } from 'pinia'
//Option Store形式
export const testStore = defineStore('testStore', {
state: () => {
return {
// 所有这些属性都将自动推断出它们的类型
age: 18,
name: 'VapausQiBlog',
}
},
actions: {
increment() {
this.age++
}
},
})

//Setup Store形式
export const testStore = defineStore('testStore', () => {
const age = ref(18)
const name = ref('VapausQiBlog')
function increment() {
age.value++
}

return { age, name, increment }
})

在这个文件里我们定义了一个 testStore,其中包含age,name两个属性以及一个increment方法供我们后续使用。

注意: defineStore接收两个参数,第一个参数是store的唯一标识(上述代码中用两种方式定义了同一个唯一标识进行了演示,实际使用中要要注释调其中一种),第二个参数接收Setup 函数或 Option 对象(上述代码已做了演示),Setup 函数更适配Vue3.x的书写规范,如果你是Vue2.x的项目或者个人书写风格觉得选项式看着更清晰一些,可以选择Option Store形式的书写规范。

4、使用 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
33
34
35
36
37
38
39
<script setup>
import { testStore } from "./store/index.js"; //引入store

const testData = testStore(); //使用store
const addAge = () => {
testData.increment();
}

</script>

<template>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
姓名:{{testData.name}}
年龄:{{testData.age}}
<button @click="addAge">点击增加年龄</button>
</div>
</template>

<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

在上述代码中,我们定义了testData来接收store,并在页面中将姓名和年龄都展示出来,并且点击增加年龄按钮的时候,页面数据也能实时获取到store中的最新值。接下老让我们看下页面及VueTools中的对应状态变化情况:

如果,开发者觉得上述使用testData一直访问属性太过繁琐,我们还可以使用解构的形式来获取,接下来我们改造上述代码

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
37
38
39
40
41
<script setup>
import { testStore } from "./store/index.js";
import {storeToRefs} from "pinia";

const {age, name} = storeToRefs(testStore());
const {increment} = testStore();
const addAge = () => {
increment();
}

</script>

<template>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
姓名:{{name}}
年龄:{{age}}
<button @click="addAge">点击增加年龄</button>
</div>
</template>

<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

注意:

1、解构后会使数据失去响应式,所以我们要使用storeToRefs,将解构的数据重新变成响应式。

2、storeToRefs 会将 Pinia store 的属性转换为 refs,使得它们可以被 Vue 的响应式系统检测到变化。然而,storeToRefs 只对 state 进行转换,不包括 actions,所以increment 方法要单独解构并且不能使用storeToRefs包裹。

接下来我们看一下实现效果:

可以看到数据状态及方法还是正常的。

5、修改store中值的几种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
//1、通过action修改
const testData = testStore();
const addAge = () => {
testData.increment();
}

//2、直接修改
const testData = testStore();
testData.age = 19;

//3、通过api 修改
const testData = testStore();
testData.$patch({age: 19, name: '张三'});
</script>

6、Pinia的插件

Pinia插件支撑了Pinia的扩展,它允许你添加全局状态、修改状态、添加全局方法等。支持一下等功能:

为 store 添加新的属性

定义 store 时增加新的选项

为 store 增加新的方法

包装现有的方法

改变甚至取消 action

实现副作用,如本地存储

仅应用插件于特定 store

更多插件的功能请查看官方的插件文档

结语

以上就是对pinia的介绍及使用,相信大家已经对pinia有了一个基本的了解,如果有不对的地方也希望大家指正。