Pinia知识点汇总,一文学会Pinia~🍍

2,508 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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有三个核心概念:

  • stategettersactions
  • 等同于组件的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);
});