Pinia快速入门

Pinia简介

官方网站

Pinia是Vue生态里Vuex的代替者,一个全新Vue的状态管理库

优点

  • Vue2Vue3都支持,这让我们同时使用Vue2Vue3的小伙伴都能很快上手
  • 和 Vuex 对比,取消了Mutations操作,只有 state getters actions 简化状态库管理
  • 完全支持 TypeScript
  • 无需再创建各个模块嵌套了,Vuex中如果数据过多,通常分模块来进行管理,稍显麻烦,而pinia中每个store都是独立的,互相不影响
  • 体积非常小,只有1KB左右。
  • pinia支持插件来扩展自身功能。
  • 支持服务端渲染

Pinia基础使用

安装Pinia

1
npm install pinia

安装完成后我们需要将pinia挂载到Vue应用中,也就是我们需要创建一个根存储传递给应用程序,简单来说就是创建一个存储数据的数据桶,放到应用程序中去。

修改main.js,引入pinia提供的createPinia方法,创建根存储。

1
2
3
4
5
6
7
8
9
10
11
12
13
// main.ts

import { createApp } from "vue";
import App from "./App.vue";

import { createPinia } from "pinia";
const pinia = createPinia();

const app = createApp(App);

app.use(pinia);
app.mount("#app");

创建Store

Store就是数据仓库的意思,也可以理解为公共组件。只不过该公共组件只存放数据,这些数据我们其它所有的组件都能够访问且可以修改。

方法

  1. 在项目src目录下新建store文件夹,用来存放所有store。例如在该目录下新建user.ts文件,用来存放与user相关的store

  2. 使用pinia提供的defineStore()方法来创建一个store,该store用来存放我们需要全局使用的数据。

代码:

1
2
3
4
5
6
7
8
 // /src/store/user.ts

import { defineStore } from 'pinia'

// 第一个参数是应用程序 store 的唯一 id
export const useUsersStore = defineStore('users', {
// 其它配置项
})

创建store很简单,调用pinia中的defineStore函数即可,该函数接收两个参数:

  • name:一个字符串,必传项,该store的唯一id
  • options:一个对象,store的配置项,比如配置store内的数据,修改数据的方法等等。

我们可以定义任意数量的store,因为store就是一个函数。

使用Store

1
2
3
4
5
6
7
// /src/App.vue
// 直接引入声明的useUserStore方法
<script setup>
import { useUsersStore } from "../src/store/user";
const store = useUsersStore();
console.log(store);
</script>

输出

img

添加State

将我们需要存放的数据放在options对象中的state属性内。

代码

1
2
3
4
5
6
7
8
9
export const useUsersStore = defineStore("users", {
state: () => {
return {
name: "小猪课堂",
age: 25,
sex: "男",
};
},
});

注意,state接收的是一个箭头函数返回的值,它不能直接接收一个对象

操作State

读取State数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<p>姓名:{{ name }}</p>
<p>年龄:{{ age }}</p>
<p>性别:{{ sex }}</p>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { useUsersStore } from "../src/store/user";

const store = useUsersStore();

const name = ref<string>(store.name);
const age = ref<number>(store.age);
const sex = ref<string>(store.sex);
// 或者 使用结构方法
const { name, age, sex } = store;
</script>

多个组件使用State

和单组件使用State方式一样

修改State数据

如果我们想要修改Store中的数据,就引入pinia提供的storeToRefs函数,将数据变为响应式,然后直接赋值就可以实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<p>姓名:{{ name }}</p>
<p>年龄:{{ age }}</p>
<p>性别:{{ sex }}</p>
<button @click="changeName">更改姓名</button>
</template>

<script setup lang="ts">
import child from './child.vue';
import { useUsersStore } from "../src/store/user";
import { storeToRefs } from 'pinia'; // 新添加
const store = useUsersStore();

const { name, age, sex } = storeToRefs(store); // 新添加
const changeName = () => {
store.name = "张三";
console.log(store);
};
</script>

重置State

直接调用Store$reset()方法即可

1
2
3
4
5
<button @click="reset">重置store</button>
// 重置store
const reset = () => {
store.$reset();
};

当我们点击重置按钮时,store中的数据会变为初始状态,页面也会更新。

批量更改State中的数据

使用store$patch方法,添加一个批量更改部分数据的方法。

1
2
3
4
5
6
7
8
9
<button @click="patchStore">批量修改数据</button>
// 批量修改数据
const patchStore = () => {
store.$patch({
name: "张三",
age: 100,
sex: "女",
});
};

原理

1
2
3
4
store.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})

直接替换整个State

pinia提供了方法让我们直接替换整个state对象,使用store$state方法。

1
store.$state = { counter: 666, name: '张三' }

这会将我们提前声明的state替换为新的对象,可能这种场景用得比较少。

getters属性

gettersdefineStore参数配置项里面的另一个属性。

getter属性值是一个对象,该对象里面有各种方法。我们可以把getter想象成Vue中的计算属性,它的作用就是返回一个新的结果,既然它和Vue中的计算属性类似,那么它肯定也是会被缓存的,就和computed一样。

当然我们这里的getter就是处理state数据。

添加getter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export const useUsersStore = defineStore("users", {
state: () => {
return {
name: "小猪课堂",
age: 25,
sex: "男",
};
},
getters: {
getAddAge: (state) => {
return state.age + 100;
},
},
});

我们在配置项参数中添加了getter属性,该属性对象中定义了一个getAddAge方法,该方法会默认接收一个state参数,也就是state对象,然后该方法返回的是一个新的数据。

使用getter

接下来我们看一下组件中如何使用getter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<p>新年龄:{{ store.getAddAge }}</p>
/* 新添加 */
<button @click="patchStore">批量修改数据</button>
</template>
<script setup lang="ts">
import { useUsersStore } from "../src/store/user";
const store = useUsersStore();
// 批量修改数据
const patchStore = () => {
store.$patch({
name: "张三",
age: 100,
sex: "女",
});
};
</script>

代码中我们直接在标签上使用了store.gettAddAge方法,这样可以保证响应式。

其实我们state中的name等属性也可以以此种方式直接在标签上使用,也可以保持响应式。

getter中调用其它getter

前面我们的getAddAge方法只是简单的使用了state方法,但是有时候我们需要在这一个getter方法中调用其它getter方法,这个时候如何调用呢?

  • 其实很简单,我们可以直接在getter方法中调用thisthis指向的便是store实例,所以理所当然的能够调用到其它getter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export const useUsersStore = defineStore("users", {
state: () => {
return {
name: "小猪课堂",
age: 25,
sex: "男",
};
},
getters: {
getAddAge: (state) => {
return state.age + 100;
},
// 新添加方法
getNameAndAge(): string {
return this.name + this.getAddAge; // 调用其它getter
},
},
});

细心的小伙伴可能会发现我们这里没有使用箭头函数的形式,这是因为我们在函数内部使用了this,箭头函数的this指向问题相信大家都知道吧!所以这里我们没有采用箭头函数的形式。

那么在组件中调用的形式没什么变化,代码如下:

1
<p>调用其它getter:{{ store.getNameAndAge }}</p>

getter传参

既然getter函数做了一些计算或者处理,那么我们很可能会需要传递参数给getter函数,但是我们前面说getter函数就相当于store的计算属性,和vue的计算属性差不多,那么我们都知道Vue中计算属性是不能直接传递参数的,所以我们这里的getter函数如果要接受参数的话,也是需要做处理的。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export const useUsersStore = defineStore("users", {
state: () => {
return {
name: "小猪课堂",
age: 25,
sex: "男",
};
},
getters: {
getAddAge: (state) => {
return (num: number) => state.age + num; // 传参
},
getNameAndAge(): string {
return this.name + this.getAddAge; // 调用其它getter
},
},
});

上段代码中我们getter函数getAddAge接收了一个参数num,这种写法其实有点闭包的概念在里面了,相当于我们整体返回了一个新的函数,并且将state传入了新的函数。

接下来我们在组件中使用,方式很简单,代码如下:

1
<p>新年龄:{{ store.getAddAge(1100) }}</p>

actions属性

前面我们提到的stategetters属性都主要是数据层面的,并没有具体的业务逻辑代码,它们两个就和我们组件代码中的data数据和computed计算属性一样。

那么,如果我们有业务代码的话,最好就是写在actions属性里面,该属性就和我们组件代码中的methods相似,用来放置一些处理业务逻辑的方法。

actions属性值同样是一个对象,该对象里面也是存储的各种各样的方法,包括同步方法异步方法

添加actions

我们可以尝试着添加一个actions方法,修改user.ts

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export const useUsersStore = defineStore("users", {
state: () => {
return {
name: "小猪课堂",
age: 25,
sex: "男",
};
},
getters: {
getAddAge: (state) => {
return (num: number) => state.age + num;
},
getNameAndAge(): string {
return this.name + this.getAddAge; // 调用其它getter
},
},
actions: {
saveName(name: string) {
this.name = name;
},
},
});

上段代码中我们定义了一个非常简单的actions方法,在实际场景中,该方法可以是任何逻辑,比如发送请求、存储token等等。大家把actions方法当作一个普通的方法即可,特殊之处在于该方法内部的this指向的是当前store

使用actions

使用actions中的方法也非常简单,比如我们在App.vue中想要调用该方法。

代码如下:

1
2
3
const saveName = () => {
store.saveName("我是小猪");
};

我们点击按钮,直接调用store中的actions方法即可。

总结示例代码

main.ts代码:

1
2
3
4
5
6
7
8
9
import { createApp } from "vue";
import App from "./App.vue";
import { createPinia } from "pinia";
const pinia = createPinia();

const app = createApp(App);

app.use(pinia);
app.mount("#app");

user.ts代码:

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
import { defineStore } from "pinia";

// 第一个参数是应用程序中 store 的唯一 id
export const useUsersStore = defineStore("users", {
state: () => {
return {
name: "小猪课堂",
age: 25,
sex: "男",
};
},
getters: {
getAddAge: (state) => {
return (num: number) => state.age + num;
},
getNameAndAge(): string {
return this.name + this.getAddAge; // 调用其它getter
},
},
actions: {
saveName(name: string) {
this.name = name;
},
},
});

App.vue代码:

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
42
43
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<p>姓名:{{ name }}</p>
<p>年龄:{{ age }}</p>
<p>性别:{{ sex }}</p>
<p>新年龄:{{ store.getAddAge(1100) }}</p>
<p>调用其它getter:{{ store.getNameAndAge }}</p>
<button @click="changeName">更改姓名</button>
<button @click="reset">重置store</button>
<button @click="patchStore">批量修改数据</button>
<button @click="saveName">调用aciton</button>


<!-- 子组件 -->
<child></child>
</template>
<script setup lang="ts">
import child from "./child.vue";
import { useUsersStore } from "../src/store/user";
import { storeToRefs } from "pinia";
const store = useUsersStore();
const { name, age, sex } = storeToRefs(store);
const changeName = () => {
store.name = "张三";
console.log(store);
};
// 重置store
const reset = () => {
store.$reset();
};
// 批量修改数据
const patchStore = () => {
store.$patch({
name: "张三",
age: 100,
sex: "女",
});
};
// 调用actions方法
const saveName = () => {
store.saveName("我是小猪");
};
</script>

child.vue代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<h1>我是child组件</h1>
<p>姓名:{{ name }}</p>
<p>年龄:{{ age }}</p>
<p>性别:{{ sex }}</p>
<button @click="changeName">更改姓名</button>
</template>
<script setup lang="ts">
import { useUsersStore } from "../src/store/user";
import { storeToRefs } from 'pinia';
const store = useUsersStore();
const { name, age, sex } = storeToRefs(store);
const changeName = () => {
store.name = "小猪课堂";
};
</script>

总结

pinia的知识点很少,如果你有Vuex基础,那么学起来更是易如反掌。其实我们更应该关注的是它的函数思想,大家有没有发现我们在Vue3中的所有东西似乎都可以用一个函数来表示,pinia也是延续了这种思想。

所以,大家理解这种组合式编程的思想更重要,pinia无非就是以下3个大点:

  • state
  • getters
  • actions

当然,本篇文章只是讲解了基础使用部分,但是在实际工作中也能满足大部分需求了。


参考文章:

小猪课堂:大菠萝!这一次彻底搞懂Pinia!(保姆级教程)

东方小月:一文解析Pinia和Vuex,带你全面理解这两个Vue状态管理模式