携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
本文主要讲解了基于Vue3Composition API的Pinia各API的使用方法,如果想了解如何在Options API中使用Pinia,参考Pinia官方文档
一、什么是Pinia
Pinia(发音为/piːnjʌ/,如英语中的“peenya”)是最接近piña(西班牙语中的菠萝)的词;
- Pinia开始于大概2019年,最初是作为一个实验为Vue重新设计状态管理,让它用起来像组合式API(Composition API)。
- 从那时到现在,最初的设计原则依然是相同的,并且目前同时兼容Vue2、Vue3,也并不要求你使用Composition API;
- Pinia本质上依然是一个状态管理的库,用于 跨组件、页面进行状态共享(这点和Vuex、Redux一样);
二、Pinia与Vuex的区别
我们不是已经有Vuex了吗?为什么还要用Pinia呢?
- Pinia 最初是为了探索 Vuex 的下一次迭代会是什么样子,结合了 Vuex 5 核心团队讨论中的许多想法;
- 最终,团队意识到Pinia已经实现了Vuex5中大部分内容,所以最终决定用Pinia来替代Vuex;
- 与 Vuex 相比,Pinia 提供了一个更简单的 API,提供了 Composition-API 风格的 API;
- 最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持;
和Vuex相比,Pinia有很多的优势:
1)比如mutations 不再存在;
2)更友好的TypeScript支持,Vuex之前对TS的支持很不友好; 3)不再有modules的嵌套结构:
- 可以创建多个store,并且灵活使用每一个store,它们是通过扁平化的方式来相互使用的;
4)也不再有命名空间的概念,不需要记住它们的复杂关系;
三、Pinia的基本使用
安装Pinia
在使用Pinia之前我们需要对Pinia进行安装:
yarn add pinia
# 或者使用npm
npm install pinia
创建Pinia
src/store/index.js
import { createPinia } from 'pinia'
const pinia = createPinia()
export default pinia
src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import pinia from './store'
createApp(App).use(pinia).mount('#app')
定义store
什么是store?
- 一个 Store 是一个实体,它会持有绑定到组件树的状态和业务逻辑,也就是保存了全局的状态;
- 可以在你的应用程序中定义任意数量的Store来管理你的状态;
store有三个核心概念:
- state、getters、actions
- 等同于组件的data、computed、methods;
- 一旦 store 被实例化,就可以直接在 store 上访问 state、getters 和 actions 中定义的任何属性;
如何定义store?
在Pinia中是使用defineStore() 定义的
- 第一个参数需要传入一个唯一的名称,也就是store的id,Pinia用它来将store连接到devtools
- defineStore()会返回一个函数,使用该函数便可以获取该store与其中的数据
src/store/counter.js
import { defineStore } from "pinia";
const useCounter = defineStore("counter", {
state() {
return {
count: 99,
};
},
});
export default useCounter;
使用store
此时,在组件中,我们便可以使用定义的store:
<template>
<div class="home">
<h2>Home View</h2>
<h2>count: {{ counterStore.count }}</h2>
</div>
</template>
<script setup>
import useCounter from "@/store/counter";
const counterStore = useCounter();
</script>
使用定义的store时,只需要调取defineStore返回的函数即可获取到该store实例
- 使用其中的state时,不需要像vuex通过 store实例.state.xx的方式来获取
- 直接通过 store实例.xx获取即可
我们可以通过devtools来清楚查看到定义的store:
四、State
定义State
在Pinia中,状态(State)被定义为返回初始状态的函数:
import { defineStore } from "pinia";
const useCounter = defineStore("counter", {
state: () => ({
count: 99,
list: [
{
id: 1,
name: "a",
},
{
id: 2,
name: "b",
},
{
id: 3,
name: "c",
},
],
}),
});
export default useCounter;
操作State
Vuex中如果想修改State中的数据需要通过mutations,而在Pinia中,可以通过store来直接读取和写入状态。
修改state中的数据
<template>
<div class="home">
<h2>Home View</h2>
<h2>count: {{ counterStore.count }}</h2>
<button @click="changeState">+1</button>
</div>
</template>
<script setup>
import useCounter from "@/store/counter";
const counterStore = useCounter();
function changeState() {
counterStore.count++;
}
</script>
除了直接通过store实例来访问state中的数据修改外,pinia还支持同时修改state中的多个数据:
- 此时可以通过store实例上的$patch方法来修改state中的多个数据
比如,counter.js中定义了这样一个store:
import { defineStore } from "pinia";
const useCounter = defineStore("counter", {
state: () => ({
count: 99,
name: "zs",
age: 18,
}),
});
export default useCounter;
如果此时想同时修改state中的name与age:
<template>
<div class="home">
<h2>Home View</h2>
<h2>name: {{ counterStore.name }}</h2>
<h2>age: {{ counterStore.age }}</h2>
<button @click="update">修改</button>
</div>
</template>
<script setup>
import useCounter from "@/store/counter";
const counterStore = useCounter();
function update() {
counterStore.$patch({
name: "lisi",
age: 20,
});
}
</script>
重置state
在Pinia中,可以通过调取store实例上的$reset方法来重置state:
<template>
<div class="home">
<h2>Home View</h2>
<h2>count: {{ counterStore.count }}</h2>
<button @click="changeState">+1</button>
<button @click="reset">重置</button>
</div>
</template>
<script setup>
import useCounter from "@/store/counter";
const counterStore = useCounter();
function changeState() {
counterStore.count++;
}
function reset() {
counterStore.$reset();
}
</script>
替换state
可以通过store实例.$state = xx的方式来替换整个state。
比如,在替换前,state包含以下数据:
现通过$state来对整个state进行替换操作:
<template>
<div class="home">
<h2>Home View</h2>
<button @click="replace">替换</button>
</div>
</template>
<script setup>
import useCounter from "@/store/counter";
const counterStore = useCounter();
function replace() {
counterStore.$state = {
name: "lisi",
age: 20,
number: 222,
};
}
</script>
替换后,state中的数据如下:
注意事项
需要注意的是,不要通过解构的方式来使用store中的state,这样会失去响应式:
<template>
<div class="home">
<h2>Home View</h2>
<h2>name: {{ name }}</h2>
<h2>age: {{ age }}</h2>
<h2>level: {{ level }}</h2>
<button @click="changeState">修改state</button>
</div>
</template>
<script setup>
import useUser from "@/store/user";
import { storeToRefs } from "pinia";
const userStore = useUser();
// 如果通过解构来访问store中的数据,那么在修改时页面上的数据不会发生改变
const { name, age, level } = userStore;
function changeState() {
userStore.$patch({
name: "james",
age: 35,
});
}
</script>
<style scoped></style>
为了保证数据的响应式,我们可以通过Pinia中的storeToRefs来使解构的数据变为响应式:
import { storeToRefs } from "pinia";
const userStore = useUser();
const { name, age, level } = storeToRefs(userStore);
五、Getters
与vuex中getters作用一样,Pinia中getters也相当于Store的计算属性。
定义Getters
通过defineStore中的getters属性去定义,在其中定义一个个接收state为参数的函数:
import { defineStore } from "pinia";
const useCounter = defineStore("counter", {
state: () => ({
count: 99,
age: 18,
firstName: "123",
lastName: "456",
}),
getters: {
doubleCount: (state) => state.count * 2,
fullName: (state) => state.firstName + state.lastName,
},
});
export default useCounter;
使用Getters
与vuex不同的时,在Pinia中可以通过store实例直接访问getters中的内容:
<template>
<div class="home">
<h2>Home View</h2>
<h2>double Count: {{ counterStore.doubleCount }}</h2>
<h2>full Name: {{ counterStore.fullName }}</h2>
</div>
</template>
<script setup>
import useCounter from "@/store/counter";
const counterStore = useCounter();
</script>
如果想在Getters中如果想要访问本身的其他Getters,可直接通过this来访问:
- 此时需要注意的是,该Getters就不能写成箭头函数的形式了(箭头函数不绑定this)
import { defineStore } from "pinia";
const useCounter = defineStore("counter", {
state: () => ({
count: 99,
age: 18,
firstName: "123",
lastName: "456",
}),
getters: {
doubleCount: (state) => state.count * 2,
fullName: (state) => state.firstName + state.lastName,
doubleCountPlusOne: function (state) {
return this.doubleCount + 1;
},
},
});
export default useCounter;
同时,也可以在Getters中访问其他store中的Getters:
import { defineStore } from "pinia";
import useUser from "./user";
const useCounter = defineStore("counter", {
state: () => ({
firstName: "123",
lastName: "456",
}),
getters: {
fullName: (state) => state.firstName + state.lastName,
message: function (state) {
const userStore = useUser();
return this.fullName + userStore.name;
},
},
});
export default useCounter;
如果向为Getters传递参数,我们可以通过在getters中返回一个接收参数的函数来实现:
import { defineStore } from "pinia";
const useUser = defineStore("user", {
state: () => ({
users: [
{
id: 111,
name: "a",
},
{
id: 222,
name: "b",
},
{
id: 333,
name: "c",
},
],
}),
getters: {
getUserById(state) {
return (userId) => {
return state.users.find((item) => item.id === userId);
};
},
},
});
export default useUser;
<template>
<div class="home">
<h2>Home View</h2>
<h2>user1: {{ getUserById(111)?.name }}</h2>
<h2>user2: {{ getUserById(222)?.name }}</h2>
</div>
</template>
<script setup>
import useUser from "@/store/user";
const userStore = useUser();
const getUserById = userStore.getUserById;
</script>
六、Actions
可以通过defineStore中的actions属性来对action进行配置,非常适合在其中编写业务逻辑。
定义Actions
在action中,可以通过this来访问store实例中的数据
import { defineStore } from "pinia";
const useCounter = defineStore("counter", {
state: () => ({
count: 99,
}),
actions: {
increment() {
this.count++;
},
// 可以为actions中定义的方法传递参数
incrementByPayload(payload) {
this.count += payload;
},
randomCount() {
this.count = Math.random();
},
},
});
export default useCounter;
使用Actions
在Pinia中可以通过store实例去直接调取Actions中定义的方法
<template>
<div class="home">
<h2>Home View</h2>
<h2>count: {{ counterStore.count }}</h2>
<button @click="increment">increment</button>
<button @click="random">random</button>
<button @click="incrementBy">incrementBy</button>
</div>
</template>
<script setup>
import useCounter from "@/store/counter";
const counterStore = useCounter();
function increment() {
counterStore.increment();
}
function random() {
counterStore.randomCount();
}
function incrementBy() {
counterStore.incrementByPayload(10);
}
</script>
Actions中定义异步函数
Pinia中的Actions也是支持定义异步函数的:
import { defineStore } from "pinia";
const useCounter = defineStore("counter", {
state: () => ({
count: 99,
}),
actions: {
async fetchData() {
const res = await fetch("...");
return res;
},
},
});
export default useCounter;
import useCounter from "@/store/counter";
const counterStore = useCounter();
counterStore.fetchData().then((res) => {
console.log(res);
});