Pinia

前言:

    Pinia其目的是设计一个拥有组合式API的Vue状态管理库。并且不强制要求开发者使用组合式API。

  • 基本用法:
pinia
// stores/counter.js
import { defineStore } from "pinia";
import { ref } from "vue";

export const useCounterStore = defineStore("counter", () => {
  const count = ref(0);
  return {
    counter,
  };
});

/* 然后你就可以在一个组件中使用该 store 了 */
import { useCounterStore } from "@/stores/counter";

const counterStore = useCounterStore();

counterStore.count++;

疑问

为什么这里通过ref声明的代理不需要使用.value来进行访问呢?

    核心原因:Pinia帮你做了"拆箱",当你从Store中导出count时,Pinia会自动处理这个ref。在Store内部,它是一个标准的ref,你修改它时必须用count.value++。但在组件中,当你通过const store = useCounterStore()拿到它时,Pinia已经把这些属性挂载到store这个响应式对象上了。

    由于store本身是一个通过reactive包装的对象,根据Vue的底层规则当一个ref作为reactive对象的属性时,它会自动解包

pinia
/* 伪代码理解 */
const store = reactive({
  count: ref(0) /* 这里会自动解包,所以store.count直接拿到0 */,
});

开始

注册Pinia

javascript
import { createApp } from "vue"
import { createPinia } from "pinia"
import App from "./App.vue"

const pinia = createPinia()
const app = createApp()

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

Store是什么

    Store(如pinia)是一个保存状态业务逻辑的实体,它并不与你得组件树绑定。换句话说,它承载着全局状态它有点像一个永远存在的组件每个组件都可以读取和写入它。它有三个概念,stategetteraction,我们可以假设这些概念相当于组件中的datacomputedmethods


核心概念

1. 定义Store

在深入研究核心概念之前,我们得知道 Store 是用 defineStore() 定义的,它的第一个参数要求是一个独一无二的名字:

javascript
import { defineStore } from "pinia";

//  defineStore()的返回值的命名是自由的
//  但最好含有 store 的名字,且以 use 开头,以 Store 结尾。
//  (比如 useUserStore,useCartStore,useProductStore)
//  第一个参数是你的应用中 Store 的唯一 ID。
export const useAlertsStore = defineStore("alerts", {
  // 其他配置...
});
  • 这个名字 ,也被用作 id ,是必须传入的, Pinia 将用它来连接 storedevtools。为了养成习惯性的用法,将返回的函数命名为 use... 是一个符合组合式函数风格的约定

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


Setup Store

  • Options Store略过,需要自行查阅官方文档。
javascript
import { defineStore } from "pinia";
import { ref, computed } from "vue";

export const userCounterStore = defineStore("count", () => {
  const count = ref(0);
  const name = ref("Eduardo");

  const doubleCount = computed(() => count.value * 2);

  function increment() {
    count.value++;
  }

  return { count, name, doubleCount, increment };
});
  • Setup Store中:ref()就是state属性,computed()就是gettersfunction()就是actions

注意

    要让 pinia 正确识别 state,你必须在 setup store 中返回 state的所有属性。这意味着,你不能在store中使用私有属性。不完整返回会影响 SSR开发工具其他插件正常运行

    Setup storeOption Store 带来了更多的灵活性,因为你可以在一个 store 内创建侦听器,并自由地使用任何组合式函数。不过,请记住,使用组合式函数会让 SSR 变得更加复杂


如果我真的想要"私有"怎么处理

如果你觉得某个变量暴露给组件很危险,或者不希望组件误改它,常见的做法是:依然在Store中返回它,但在命名上做约定(比如加下划线),或者只暴露readonly的版本:

JavaScript
export const useUserStore = defineStore("user", () => {
  const _internalState = ref(0); /* 约定下划线开头为私有 */

  return {
    _internalState /* 为了 SSR 和插件,必须返回 */,
    count: readonly(_internalState) /* 组件只能读,不能直接改 */,
  };
});

    setup store 也可以依赖于全局提供的属性,比如路由。任何应用层面提供的属性都可以在 store 中使用 inject() 访问,就像在组件中一样:

javascript
import { inject } from "vue";
import { useRoute } from "vue-router";
import { defineStore } from "pinia";

export const useSearchFilters = defineStore("search-filters", () => {
  const route = useRoute();
  // 这里假定 `app.provide('appProvided', 'value')` 已经调用过
  const appProvided = inject("appProvided");

  // ...

  return {
    // ...
  };
});
提示!

    不要返回像 routeappProvided (上例中)之类的属性,因为它们不属于 store,而且你可以在组件中直接用 useRoute()inject('appProvided') 访问。


使用Store

  • 你可以定义任意多store,但为了让使用 pinia益处最大化(比如允许构建工具自动进行代码分割以及 TypeScript 推断),你应该在不同的文件中去定义 store

  • 一旦 store 被实例化,你可以直接访问在 storestategettersactions 中定义的任何属性。我们将在后续章节继续了解这些细节,目前自动补全将帮助你使用相关属性。

  • 请注意,store 是一个用 reactive 包装的对象,这意味着不需要在 getters 后面写 .value。就像 setup 中的 props 一样,我们不能对它进行解构

vue
<script setup>
import { useCounterStore } from "@/stores/counter";
import { computed } from "vue";

const store = useCounterStore();
// ❌ 下面这部分代码不会生效,因为它的响应式被破坏了
// 与 reactive 相同: https://vuejs.org/guide/essentials/reactivity-fundamentals.html#limitations-of-reactive
const { name, doubleCount } = store;
name; // 将会一直是 "Eduardo"
doubleCount; // 将会一直是 0
setTimeout(() => {
  store.increment();
}, 1000);
// ✅ 而这一部分代码就会维持响应式
// 💡 在这里你也可以直接使用 `store.doubleCount`
const doubleValue = computed(() => store.doubleCount);
</script>

从 Store 解构

为了从 store 中提取属性时保持其响应性,你需要使用 storeToRefs()它将为每一个响应式属性创建引用。当你只使用 store状态而不调用任何 action 时,它会非常有用。请注意,你可以直接从 store 中解构 action,因为它们也被绑定到 store 上:

vue
<script setup>
  import { storeToRefs } from "pinia";
  const store = useCounterStore();
  // `name` 和 `doubleCount` 都是响应式引用
  // 下面的代码同样会提取那些来自插件的属性的响应式引用
  // 但是会跳过所有的 action 或者非响应式(非 ref 或者 非 reactive)的属性
  const { name, doubleCount } = storeToRefs(store);
  // 名为 increment 的 action 可以被解构
  const { increment } = store;
</script>