Skip to content

Pinia 介绍

Pinia 是一个专门为 Vue 设计的状态管理库,它提供了一种简单和直观的方式来管理应用程序的状态.

在使用 Pinia 之前,可以轻松的创建定义状态的存储,然后将其与 Vue 组件绑定,使他们能够使用该状态.

Pinia 更加简单易用,体积更小,同时具有更好的 TS 支持和插件系统

安装和配置 Pinia

安装和配置 Pinia 非常简单,像其他 Vue 插件一样,Pinia 需要通过 yarn 和 npm 进行安装 并且与 Vue 应用程序进行绑定

安装命令

js

yarn add pinia

npm install pinia

在安装完 Pinia 包之后,需要在 main.js 文件中导入 createPinia 函数并将 Pinia 插件与 Vue 应用程序绑定:如下

js
import { createApp } from "vue";

import { createPinia } from "pinia";

import App from "./App.vue";

const app = createApp(App);

const pinia = createPinia();

app.use(pinia);

app.mount("#app");

使用 createPinia() 函数创建并初始化 Pinia 插件实例,将其与 Vue 应用程序绑定使用 app.use(pinia)。至此,我们就可以使用 Pinia 来管理 Vue 应用程序的状态了。

Pinia 的核心

Store

Store 是 Pinia 中管理状态的核心理念.它相当于一个 Vue 组件中的状态,但是 Store 是一个独立的模块

Store 是用 defineStore()来定义的,它的第一个参数要求是一个独一无二的的名字, 这个名字也被用作 id,是必须传入的.

Pinia 将用它来链接 store 和 devtools 为了养成习惯性的用法,将返回函数命名为 use...是一个符合组合式函数风格的约定

defineStore()的第二个参数可接受两类值:Setup 函数或者 Option 对象

定义 Store 的示例代码

js
import { defineStore } from "pinia";

// 你可以对 `defineStore()` 的返回值进行任意命名,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。(比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useAlertsStore = defineStore("alerts", {
  // 其他配置...
});

State

State 是 store 中存储数据的地方.通过定义 State,可以在 Store 任意位置访问和修改数据

在 Pinia 中,state 被定义为一个返回初始状态的函数,这使的 Pinia 可以同时支持服务器端和客户端

定义 State 的示例代码如下:

js
import { defineStore } from "pinia";

const useStore = defineStore("storeId", {
  // 为了完整类型推理,推荐使用箭头函数
  state: () => {
    return {
      // 所有这些属性都将自动推断出它们的类型
      count: 0,
      name: "Eduardo",
      isAdmin: true,
      items: [],
      hasChanged: true,
    };
  },
});

Getter

Getter 用来获取从 State 派生的数据,类似于 Vue 组件中的 computed 计算属性.可以通过 defineStore()中的 getters 属性来定义它们

推荐使用箭头函数,并且它将接收 state 作为第一个参数

箭头函数语法()=>({}) 一定要加括号,表示return括号里面的对象

js

import { defineStore } from "pinia";

export const useStore = defineStore('main', {
  state:()=>({
    count:0
  })
  getters: {
    doubleCount: (state) => state.count * 2,
  },
})

Action

Action 相当于组件中的方法,它们可以通过 defineStore()中的 actions 属性来定义,Action 是一种异步操作封装在 Store 中的方式

它可以是一个可以被调用的函数.也可以接收参数并修改 store 中的状态.Action 应该始终是同步的,并返回一个 Promise 对象

以便处理异步操作的时候能够很好处理结果

Pinia 中的 Action 是由 defineStore 创建,可以通过在 actions 中定义它们来使用它们。例如下面是一个 store 中的 Action 定义

js
import { defineStore } from "pinia";

export const myStore = defineStore("mystore", {
  state: () => ({ message: "Hello", count: 0 }),
  getters: {
    doublecount: () => {
      return state.count++;
    },
  },
  actions: {
    async fetchmessage() {
      const response = await fetch("https://xxxx.com/php");
      const data = await response.json();
      this.message = data.message;
    },
  },
});

在上面的示例中,我们为 myStore 定义了一个 Action , fetchMessage() ,它会从后台 API 中获取数据,并更新 store 中的状态。然后,我们可以从组件或其他 Action 中调用该 Action :

html
<template>
  <div>
    <button @click="changestate">点击开始</button>
  </div>
</template>

<script setup>
  import { useLoginStore } from "@/stores/login.js";
  import { useCounterStore } from "@/stores/counter.js";
  const login = useLoginStore();
  const counter = useCounterStore();
  const changestate = () => {
    console.log("点击了");
    login.changeLoginflag(true);
    console.log(counter.count);
  };
</script>

<style lang="scss" scoped></style>

在上面的代码中,我们在组件中使用 useStore 钩子来获取 store 实例,然后将其传递给 fetchMessage() 方法。该方法将从应用程序的后台获取数据,并更新存储器中的状态。最后,公开了一个 handleClick() 方法,以便组件可以调用它并触发 Action 。

创建和使用 Pinia

创建 Pinia

前面我们已经安装和配置好了 Pinia,在创建 Pinia 之前,为了代码的统一管理和可维护性,我们依然先创建一个 store 文件夹,然后在来创建相关的 Pinia,具体步骤如下

  • 在 src 文件夹下新建 store 文件夹,后面所有涉及需要 Pinia 进行状态管理的代码都放在该文件夹下

  • 在 store 文件夹下新建 movieListStore.js 文件,创建完成后,打开该文件

  • 在 movieListStore.js 文件中引入 Pinia 中的 defineStore 方法

js
import { defineStore } from "pinia";
  • 创建 defineStore 对象,定义一个 useMovieListStore 用于接收 defineStore 创建的对象,并将其通过 export default 导出
js
import { ref, computed } from "vue";
import { defineStore } from "pinia";

export const useCounterStore = defineStore("counter", () => {
  const count = ref(0);
  const doubleCount = computed(() => count.value * 2);
  function increment() {
    count.value++;
  }

  return { count, doubleCount, increment };
});

在上面的代码中,我们使用 action 定义了两个方法,

一个同步方法 setIsShow,

一个异步方法 fetchMovies

注意:

这里需要注意,官方建议我们在定义钩子函数时,建议使用 use 开头 Store 结尾的命名方式来对上面创建的对象进行命名,如上面的 useMovieListStore

使用 Pinia

前面我们已经创建好了 Pinia,接下来,我们就可以在组件中使用了。

在 Vue 组件中使用 store,我们需要通过 useStore() 函数访问 store 的实例。

在 Vue 组件中使用 Pinia 的步骤如下:

  • 先使用 import 引入 模块
js
import { useLoginStore } from "@/stores/login.js";
  • 创建 useStore 对象
js
const login = useLoginStore();
  • 在需要获取状态的地方通过上面定义的 store.getIsShow()获取状态
js
return {
  isShow: login.Loginflag,
};

Menu.vue 中完整的示例如下:

html
<template>
  <div>
    <button @click="changestate">点击开始</button>
  </div>
</template>

<script setup>
  import { useLoginStore } from "@/stores/login.js";
  import { useCounterStore } from "@/stores/counter.js";
  const login = useLoginStore();
  const counter = useCounterStore();
  const changestate = () => {
    console.log("点击了");
    login.changeLoginflag(true);
    console.log(counter.count);
  };
</script>

<style lang="scss" scoped></style>

Pinia 的 Option Store 方式定义 Store

Option Store 方式定义 Store 与 Vue 的选项式 API 类似,我们通过传入一个带有 state、actions 与 getters 属性的 Option 对象来定义,示例代码如下:

js
export const useCounterStore = defineStore("counter", {
  state: () => ({ count: 0 }),
  getters: {
    double: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++;
    },
  },
});

我们可以认为 state 是 store 的数据 (data),getters 是 store 的计算属性 (computed),而 actions 则是方法 (methods)。

Pinia 的 Setup Store 方式定义 Store

Setup Store 与 Option Store 稍有不同,它与 Vue 组合式 API 的 setup 函数 相似,我们通过传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。示例代码如下:

js
export const useCounterStore = defineStore("counter", () => {
  const count = ref(0);
  function increment() {
    count.value++;
  }
  return { count, increment };
});

在 Setup Store 中:

  • ref() 就是 state 属性

  • computed() 就是 getters

  • function() 就是 actions

示例代码

下面通过一个实例来完整的说明 Pinia 状态管理的使用方法,现在要实现如下效果:

现在页面上需要完成两个功能,一个功能是通过监听 isShow 的值,来控制不同页面跳转时,底部菜单栏 button 的显示和隐藏;另一个功能是通过一个函数连接网络获取电影列表,并在详情页展示出来

在选项式 API 中,实现代码如下:

js
// main.js

import "./assets/main.css";

import { createApp } from "vue";
import { createPinia } from "pinia";

import App from "./App.vue";
import router from "./router";

const app = createApp(App);

app.use(createPinia());
app.use(router);

app.mount("#app");
  1. store 文件夹下 login.js 中的代码

这里面也包含了模块直接的通信

js
import { ref } from "vue";
import { defineStore, storeToRefs } from "pinia";
import { useCounterStore } from "./counter";
export const useLoginStore = defineStore("login", () => {
  //counter模块
  const counterStore = useCounterStore();
  //解构模块
  const { count } = storeToRefs(counterStore);
  //counter模块
  let Loginflag = ref(false);

  function changeLoginflag(content) {
    console.log(content);
    Loginflag.value = content;
    count.value = 5;
  }

  return { changeLoginflag, Loginflag };
});
  1. store 文件夹下 counter.js 中的代码
js
import { ref, computed } from "vue";
import { defineStore } from "pinia";

export const useCounterStore = defineStore("counter", () => {
  const count = ref(0);
  const doubleCount = computed(() => count.value * 2);
  function increment() {
    count.value++;
  }

  return { count, doubleCount, increment };
});
  1. components 文件夹下 Menu.vue 文件的代码
html
<template>
  <div>
    <button @click="changestate">点击开始</button>
  </div>
</template>

<script setup>
  import { useLoginStore } from "@/stores/login.js";
  import { useCounterStore } from "@/stores/counter.js";
  const login = useLoginStore();
  const counter = useCounterStore();
  const changestate = () => {
    console.log("点击了");
    login.changeLoginflag(true);
    console.log(counter.count);
  };
</script>

<style lang="scss" scoped></style>