vue3的pinia大菠萝详解

612 阅读5分钟

Pinia 最初是在 2019 年 11 月左右重新设计使用 Composition API 。从那时起,最初的原则仍然相同,但 Pinia 对 Vue 2 和 Vue 3 都有效,并且不需要您使用组合 API。 除了安装和 SSR 之外,两者的 API 都是相同的,并且这些文档针对 Vue 3,并在必要时提供有关 Vue 2 的注释,以便 Vue 2 和 Vue 3 用户可以阅读!

为什么要使用 Pinia?#

Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。 如果您熟悉 Composition API,您可能会认为您已经可以通过一个简单的 export const state = reactive({}). 这对于单页应用程序来说是正确的,但如果它是服务器端呈现的,会使您的应用程序暴露于安全漏洞。 但即使在小型单页应用程序中,您也可以从使用 Pinia 中获得很多好处:

  • dev-tools 支持

    • 跟踪动作、突变的时间线
    • Store 出现在使用它们的组件中
    • time travel 和 更容易的调试
  • 热模块更换

    • 在不重新加载页面的情况下修改您的 Store
    • 在开发时保持任何现有状态
  • 插件:使用插件扩展 Pinia 功能

  • 为 JS 用户提供适当的 TypeScript 支持或 autocompletion

  • 服务器端渲染支持

与 Vuex 的比较#

Pinia 最初是为了探索 Vuex 的下一次迭代会是什么样子,结合了 Vuex 5 核心团队讨论中的许多想法。最终,我们意识到 Pinia 已经实现了我们在 Vuex 5 中想要的大部分内容,并决定实现它 取而代之的是新的建议。

与 Vuex 相比,Pinia 提供了一个更简单的 API,具有更少的规范,提供了 Composition-API 风格的 API,最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持。

安装

安装
yarn add pinia
# 或者使用 npm
npm install pinia
引入并挂载
import { createPinia } from 'pinia'
app.use(createPinia())

什么是 Store ?#

一个 Store (如 Pinia)是一个实体,它持有未绑定到您的组件树的状态和业务逻辑。换句话说,它托管全局状态。它有点像一个始终存在并且每个人都可以读取和写入的组件。它有三个概念stategetters 和 actions 并且可以安全地假设这些概念等同于组件中的“数据”、“计算”和“方法”。

我什么时候应该使用 Store

存储应该包含可以在整个应用程序中访问的数据。这包括在许多地方使用的数据,例如导航栏中显示的用户信息,以及需要通过页面保留的数据,例如一个非常复杂的多步骤表格。

另一方面,您应该避免在存储中包含可以托管在组件中的本地数据,例如页面本地元素的可见性。

并非所有应用程序都需要访问全局状态,但如果您需要一个,Pania 将使您的生活更轻松。

定义一个 Store

import { defineStore } from 'pinia'

// useStore 可以是 useUser、useCart 之类的任何东西(相当于名称,推荐use开头)
// 第一个参数是应用程序中 store 的唯一 id(必填项)
export const useStore = defineStore('main', {
  // other options...
})

这个 name,也称为 id,是必要的,Pinia 使用它来将 store 连接到 devtools。

使用 store#

我们正在 定义 一个 store,因为在 setup() 中调用 useStore() 之前不会创建 store:

import { useStore } from '@/stores/counter'//引入
const store = useStore()//定义一个仓库
store 被实例化,你就可以直接在 store 上访问 `state``getters` 和 `actions` 中定义的任何属性。

store 是一个用reactive 包裹的对象,这意味着不需要在getter 之后写.value,但是,就像setup 中的props 一样,我们不能对其进行解构

错误写法,会失去响应式
const { name, doubleCount } = store
使用storeToRefs()的写法,数据保持响应式,但无法修改
const { name, doubleCount } = storeToRefs(store)

访问 “state”#

 一般情况下可以通过store 实例直接读取和写入状态:

const store = useStore()
store.counter++

重置状态#

您可以通过调用 store 上的 $reset() 方法将状态 重置 到其初始值:

const store = useStore()

store.$reset()

除了直接用 store.counter++ 修改 store,你还可以调用 $patch 方法。 它允许您使用部分“state”对象同时应用多个更改:

store.$patch({
  counter: store.counter + 1,
  name: 'Abalam',
})

但是,使用这种语法应用某些突变非常麻烦:任何集合修改(例如,从数组中推送、删除、拼接元素)都需要创建一个新集合。 正因为如此,$patch 方法也接受一个函数来批量修改集合内部分对象的情况:

cartStore.$patch((state) => {
  state.items.push({ name: 'shoes', quantity: 1 })
  state.hasChanged = true
})

geeters属性(案例)

App.vue

<template>
  <header>
    <div>
      <div>
        <!-- 页面上可以直接使用 -->
        这是年龄--{{ userStore.age }} 双倍年龄--{{ userStore.getNameAdnAge }}
        <button @click="add">+1</button>
      </div>
      <div>
        <!-- 页面上使用的时候,需要进行传参 -->
        这个是天数{{ userStore.getAddAge(0) }}
        <button @click="add1">+1</button>
      </div>
    </div>
  </header>
</template>
<script setup lang="ts">
// 引入定义的userStore模块
import { storeToRefs } from "pinia";
import { useUserStore } from "./stores/counter";

// 使用userStore 并返回定义模块的实例
const userStore = useUserStore();
/**
 * 每次修改计算属性里所依赖的值
 * 计算属性会自动的重新计算
 */
const add = () => {
  userStore.age += 1;
};
const add1 = () => {
  userStore.day += 365;
};
</script>
<style scoped>
header {
  line-height: 1.5;
}

.logo {
  display: block;
  margin: 0 auto 2rem;
}

@media (min-width: 1024px) {
  header {
    display: flex;
    place-items: center;
    padding-right: calc(var(--section-gap) / 2);
  }

  .logo {
    margin: 0 2rem 0 0;
  }

  header .wrapper {
    display: flex;
    place-items: flex-start;
    flex-wrap: wrap;
  }
}
</style>

stores下的counter.ts

import { ref, computed } from "vue";
// 引入defineStore 方法
import { defineStore } from "pinia";
/**
 * pinia中使用defineStore定义store
 * 第一个参数是应用程序中 store 的唯一 id
 * 第二个参数是是一个对象,store的配置项,比如配置store内的数据,修改数据的方法等等。
 *
 * 返回一个函数使用use+模块名命名
 */
export const useUserStore = defineStore("user", {
  // 其它配置项

  /**
   * 定义当前的模块的state
   * state是一个函数,必须有返回值
   * 返回值就是这个模块的状态
   * @returns
   */
  state() {
    // 返回age为1
    return {
      /**
       * 年龄
       */
      age: 18,
      day: 0,
      name: "=>意会",
    };
  },

  /**
   * 计算属性在getters对象中定义
   * 定义一个方法,方法必须有返回值,返回的值就是这个数算属性的结果
   *
   * 方法接收一个参数,为当前的state
   */
  getters: {
    // 两倍年龄
    doubleAge: (state) => state.age * 2,
    getNameAdnAge(): string {
      return this.doubleAge + this.name;
    },
    
    getAddAge () {
      /**
       * 在计算属性内部,返回一个方法
       * 该方法会接收一个参数,这个参数就是
       * 页面传过来的参数
       */
      return (num: number) => this.day + num;
    }
  },
});

Actions...待更新