解锁 Vue 3 状态管理新体验 —— Pinia 大菠萝的奇幻之旅

185 阅读6分钟

引言

在现代前端开发中,状态管理是构建复杂应用时不可或缺的一部分。随着应用程序的增长,组件之间的数据共享和通信变得越来越复杂。Vue.js 作为一个流行的前端框架,其生态系统也在不断发展,以应对这些挑战。Pinia 就是在这样的背景下应运而生的,它为 Vue 3 提供了一种简洁、直观且功能强大的状态管理解决方案。


什么是 Pinia?

Pinia 是专门为 Vue 3 设计的状态管理库,它继承了 Vuex(Vue 2 中的状态管理库)的优点,并通过简化 API 和增强 TypeScript 支持,提供了更现代化的解决方案。Pinia 的核心理念是让开发者能够更轻松地管理应用中的状态,同时保持代码的清晰性和可维护性。

  • 集中式状态管理:所有状态都集中在 store 中,确保状态的一致性和可预测性。
  • 简洁的 API:不再区分 mutations 和 actions,所有操作都可以在 actions 中完成。
  • 更好的 TypeScript 支持:自动推断类型,减少手动声明类型的需要。
  • 与 Composition API 完美兼容:充分利用 Vue 3 的新特性,提供更直观的状态管理方式。
  • 插件系统和调试工具集成:支持插件扩展,并默认集成了 Vue Devtools。

如何使用Pinia

1. 搭建pinia环境

第一步:npm install pinia 第二步:操作src/main.ts

import { createApp } from 'vue'
import App from './App.vue'
//引入createPinia,用于创建pinia 
import {createPinia} from 'pinia'
//状态管理库
//pinia 的实例 vue 全家桶中的Store
const pinia = createPinia()
const app = createApp(App)
//使用插件
app
.use(pinia)
.mount('#app')

2.创建store

  • Store是一个保存:状态业务逻辑 的实体,每个组件都可以读取写入它。

  • defineStore 是 Pinia 提供的一个函数,用于定义一个新的 store。它接受两个参数:

      1. Store ID (标识符) : 一个字符串,作为 store 的唯一标识符。这个 ID 应该在整个应用中是唯一的,因为它是用来区分不同 store 的。当你在组件中使用 useStore() 来获取 store 实例时,这个 ID 就是用来匹配相应的 store。
      1. Store 定义对象: 这是一个包含了 state、getters 和 actions 的对象。它定义了 store 的行为和状态管理逻辑。这个对象可以以两种方式提供:
      • 对象选项
      • 回调函数
  • src目录下创建store目录来存储store仓库

使用对象选项

import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++;
    },
  },
});
    1. State(状态)

State 是 store 的数据源,它存储了应用中需要被共享的状态信息。你可以将 state 看作是一个容器,用来存放所有你需要在整个应用中访问的数据。

  • 2. Getters(获取器)

Getters 类似于 Vue 组件中的计算属性,它允许你从 state 中派生出一些新值,但这些值本身并不直接修改原始状态。Getters 主要用于处理依赖于 state 的逻辑计算或过滤。

    1. Actions(动作)

Actions 是用来定义改变 state 方法的地方。与 Vuex 不同的是,在 Pinia 中 actions 可以直接修改 state,而不需要通过 mutations 来间接变更。

使用回调函数

import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', () => {
  // 状态 (state)
  const count = ref(0);

  // 可以从 state 中计算值得到 getters
  const doubleCount = computed(() => count.value * 2);

  // 方法 (actions)
  function increment() {
    count.value++;
  }

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

3.组件中使用store

<template>
    <div>
        <h2>CompA</h2>
        <p>count:{{ counterStore.count }}</p>
        <p>doubleCount:{{ counterStore.doubleCount }}</p>
        <button @click="counterStore.increment">Add</button>
    </div>
</template>

<script setup>
import { useCounterStore } from '../store/counter';
const counterStore = useCounterStore();

</script>

screenshots.gif

screenshots.gif

除了借助action修改数据(action中可以编写一些业务逻辑),还有两种常用方式

  • 直接修改
countStore.count +=1 
  • 批量修改
countStore.$patch({
  count1:999,
  count2:222
})

storeToRefs

  • 借助storeToRefsstore中的数据转为ref对象,方便在模板中使用。
  • 注意:pinia提供的storeToRefs只会将数据做转换,而VuetoRefs会转换store中的所有属性。
<template>
    <div>
        <h2>CompA</h2>
        <p>count:{{ count }}</p>
        <p>doubleCount:{{ doubleCount }}</p>
        <button @click="counterStore.increment">Add</button>
    </div>
</template>

<script setup>
import { useCounterStore } from '../store/counter';
import { storeToRefs } from 'pinia';
import {toRefs} from 'vue';
const counterStore = useCounterStore();
const { count, doubleCount } = storeToRefs(counterStore);


console.log(storeToRefs(counterStore))
console.log(toRefs(counterStore))

</script>
 

image.png

image.png

总结Pinia的作用

  • 集中式状态管理

Pinia 提供了一种集中式状态管理模式,允许我们将应用中的状态集中存放在一个或多个 store 中。这种模式有助于保持代码的清晰度,并确保状态的一致性和可预测性。每个 store 是独立的,但它们可以相互关联,共同构成整个应用的状态图。

  • 组件关系与共享数据状态

父组件持有数据状态并共享给子组件

在 Vue 应用中,父子组件间的通信通常通过 props 和事件(如 v-on)来完成。父组件可以通过 props 向子组件传递数据,而子组件则可以通过自定义事件通知父组件更改状态。然而,当涉及到多个层级的嵌套组件时,这种方式可能会导致“prop-drilling”问题,即数据需要一层层传递下去,增加了维护成本。

使用 Pinia 可以有效解决这个问题。父组件不再需要直接持有所有数据状态,而是可以通过引入 store 来管理这些状态,并将 store 实例传递给子组件。这样一来,任何组件都可以访问到最新的状态,而不需要关心它是如何传递下来的。

<template>
  <div>
    <!-- 子组件可以直接访问 store -->
    <ChildComponent />
  </div>
</template>

<script setup>
import { useMainStore } from './stores/main';
const mainStore = useMainStore();
</script>
确保组件共享正确的数据状态

Pinia 保证了所有组件看到的是同一个 store 实例,因此无论组件位于何处,它们都能获取到最新且一致的状态。这大大简化了多组件协作时的状态同步问题。

兄弟组件不能直接修改 props 数据状态

兄弟组件之间不应该通过 props 直接修改对方的数据状态。这样做会导致数据流向不明确,难以追踪和调试。相反,兄弟组件应该通过共享的 store 来改变状态。store 的 actions 提供了安全的方法来更新状态,确保所有变更都是可控的。

// 正确的做法:通过 store 修改状态
<button @click="mainStore.increment">Increment</button>
  • 超越父子组件通信,实现全局状态共享

除了处理父子组件间的通信外,Pinia 还能够轻松实现跨层级甚至非父子关系组件之间的状态共享。无论是顶部导航栏、侧边栏还是模态框,只要这些组件需要访问相同的数据或触发相同的逻辑,都可以通过引入相同的 store 来完成。

javascript
深色版本
// 在任意组件中使用相同的 store
import { useMainStore } from './stores/main';

export default {
  setup() {
    const mainStore = useMainStore();

    return {
      count: computed(() => mainStore.count),
      increment: () => mainStore.increment(),
    };
  },
};

此外,Pinia 还支持插件系统,允许你扩展其功能,比如持久化存储、日志记录等。这对于需要在页面刷新后保留状态的应用非常有用。