Skip to content

Pinia?看这篇就够了!

Published: at 08:24

Pinia(发音为/piːnjʌ/,如英语中的“peenya”)是最接近 piña(西班牙语中的菠萝)的词。 Pinia本质上依然是一个全局状态管理的库,用于跨组件、页面进行状态共享(这点和Vuex、Redux、Mobx一样)。

一、Pinia 和 Vuex 的区别

二、简单使用 Pinia

// 使用 npm 安装
npm install pinia
// 使用 yarn 安装
yarn add pinia
// 使用 pnpm 安装
pnpm add pinia

Store 文件

// scr/stores/index.ts
import { createPinia } from "pinia";

const MyPinia = createPinia();

export default MyPinia;

注册全局

// src/main.ts
import { createApp } from "vue";

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

import "./assets/main.css";

import MyPinia from "./stores/index";

const app = createApp(App);

// 全局注册 pinia
app.use(MyPinia);
app.use(router);
app.mount("#app");
// scr/stores/counter.ts
import { ref, computed } from "vue";
import { defineStore } from "pinia";

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

  const increment = () => {
    count.value++;
  };
  const decrement = () => {
    count.value--;
  };

  return { count, doubleCount, increment, decrement };
});
// src/App.vue
<script setup lang="ts">
import { useCounterStore } from "@/stores/counter";
// 注意:这里导入的函数,不能被解构,那么会失去响应式。
// 例如不能这样: const { count, increment, decrement, doubleCount } = useCounterStore();
const counterStore = useCounterStore();
</script>

<template>
  <div class="app-wrapper">
    <div>count:{{ counterStore.count }}</div>
    <div>doubleCount:{{ counterStore.doubleCount }}</div>
    <div>
      <button @click="counterStore.increment()">增加</button>
      <button @click="counterStore.decrement()">减少</button>
    </div>
  </div>
</template>

<style scoped></style>
<script setup lang="ts">
import { useCounterStore } from "@/stores/counter";
import { toRefs } from "vue";
// or
// import { storeToRefs } from "pinia";
const counterStore = useCounterStore();
const { count, doubleCount, increment, decrement } = toRefs(counterStore);
</script>

<template>
  <div class="app-wrapper">
    <div>count:{{ count }}</div>
    <div>doubleCount:{{ doubleCount }}</div>
    <div>
      <button @click="increment()">增加</button>
      <button @click="decrement()">减少</button>
    </div>
  </div>
</template>

<style scoped></style>
  • 以上我们全局注册 Store 后,通过 defineStore定义了一个名为 counter的Store,namecounter,也称为 id,是必须的,Pinia 使用它来将 Store 链接到 devtools
  • 返回的函数我们使用 use开头的小驼峰格式方案命名,这是约定的规范。

Pinia 在浏览器中的调试的工具 vue-devtools ,在 Chrome 或 Edge 的扩展中直接搜索 vue-devtools 进行安装即可。

三、操作 State

默认情况下,可以通过 store 实例访问状态来直接进行读取和写入状态;

const counterStore = useCounterStore();
const add = () => {
  counterStore.count++;
};

可以直接在页面中定义函数调用,也可以写在定义 counter 的文件当中。

const counterStore = useCounterStore();
const resetStore = () => {
  // 调用 $reset 函数进行数据重置为初始值
  counterStore.$reset();
};
const counterStore = useCounterStore();
const add = () => {
  // 调用 $patch 同时应用多个修改
  counterStore.$patch({
    count: 10,
    // ...可同时修改多个属性
  });
};
const counterStore = useCounterStore();
const replaceState = () => {
  // 通过 $state 直接赋值替换整个对象
  counterStore.$state = {
    count: 100,
  };
};

四、认识和定义Getters

// scr/stores/user.ts
import { defineStore } from "pinia";
export const useUserStore = defineStore("user", {
  state: () => ({
    name: "ll",
    age: 18,
    count: 100,
  }),
  getters: {
    doubleCount(state) {
      return state.count * 2;
    },
    doubleCountAddOne(): number {
      return this.doubleCount + 1;
    },
  },
});

// scr/views/UserView.vue
<script setup lang="ts">
import { useUserStore } from "@/stores/user";
const userStore = useUserStore();
</script>

<template>
  <div class="user-wrapper">
    <h2>name:{{ userStore.name }}</h2>
    <h2>age:{{ userStore.age }}</h2>
    <h2>count:{{ userStore.count }}</h2>
    <h2>doubleCount:{{ userStore.doubleCount }}</h2>
    <h2>doubleCountAddOne:{{ userStore.doubleCountAddOne }}</h2>
  </div>
</template>

<style scoped>
.user-wrapper h2 {
  font-size: 24px;
  font-weight: bold;
}
</style>

渲染效果:

访问 getter 中定义的 doubleCount ,并且可以访问其他的 getter doubleCountAddOne

// scr/stores/user.ts
state: () => ({
  userList: [
    {
      id: 1,
      name: "ll",
    },
    {
      id: 2,
      name: "zz",
    },
  ],
}),
getters: {
  getUserById(state) {
    return (id: number) => state.userList.find((item) => item.id === id);
  },
}
// scr/views/UserView.vue
<h2>id1:{{ userStore.getUserById(1) }}</h2>
<h2>id2:{{ userStore.getUserById(2) }}</h2>

渲染效果:

import { useCounterStore } from "@/stores/counter";
otherGetter(state) {
  const counterStore = useCounterStore();
  return state.count + counterStore.count;
}

五、认识和定义Actions

// scr/stores/home.ts
import { defineStore } from "pinia";
export const useHomeStore = defineStore("home", {
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++;
    },
    randomCount() {
      this.count = Math.round(Math.random() * 100);
    },
    setData(val: number) {
      this.count = val;
    },
  },
});
<!-- scr/views/HomeView.vue-->
<script setup lang="ts">
import { useHomeStore } from "@/stores/home";
const homeStore = useHomeStore();
</script>

<template>
  <div class="home-wrapper">
    <h2>count:{{ homeStore.count }}</h2>
    <div>
      <button @click="homeStore.increment">增加</button>
      <button @click="homeStore.randomCount">随机</button>
      <button @click="homeStore.setData(666)">设置</button>
    </div>
  </div>
</template>

<style scoped>
.home-wrapper h2 {
  font-size: 24px;
  font-weight: bold;
}
</style>

以上通过 Actions 定义的方法操作视图更新,并且可以通过调用函数传递参数来直接设置数据

export const useHomeStore = defineStore("home", {
  state: () => ({
    banners: [],
  }),
  actions: {
    async fetchHomeBanner() {
      const res = await fetch("http://123.207.32.32:8000/home/multidata");
      const data = await res.json();
      this.banners = data.data.banner.list;
    },
  },
});
<script setup lang="ts">
import { useHomeStore } from "@/stores/home";
const homeStore = useHomeStore();
</script>

<template>
  <div class="home-wrapper">
    <h2>banners:</h2>
    <ul>
      <template v-for="item in homeStore.banners" :key="item.id">
        <li>{{ item.title }}</li>
      </template>
    </ul>
    <div>
      <button @click="homeStore.fetchHomeBanner">获取数据</button>
    </div>
  </div>
</template>

<style scoped>
.home-wrapper h2 {
  font-size: 24px;
  font-weight: bold;
}
</style>

你可以完全自由地设置你想要的任何参数并返回任何数据。 调用 Action 时,一切都会自动推断!Actions 像 methods 一样被调用。

六、总结

Pinia 相较于 Vuex 来说,更简单的API,更少的写法,并且对于TypeScript 支持非常好,可以自动推断类型,可以随意调用,不用局限于 mutations,大大提高了开发效率,并且降低了学习成本。


Previous Post
Antd Form.List 嵌套多级动态增减表单以及给某个表单提示校验
Next Post
体操支配的恐惧之 TypeScript 类型编程的意义