Vue 3 + TypeScript 使用 Pinia 进行状态管理

862 阅读6分钟

介绍

在 Vue 3 中,状态管理是构建大型应用不可或缺的一部分。Pinia 是一个专为 Vue 3 设计的状态管理库,它提供了一种简洁、优雅的方式来管理组件间共享的状态。本文将介绍 Pinia 的基本概念、安装配置方法以及如何在 Vue 应用中使用 Pinia 进行状态管理,并且探讨如何集成持久化插件来确保应用状态的持久性。

什么是 Pinia?

Pinia 是一个基于 Vue 3 的状态管理库,它提供了一种响应式、强类型的方式来管理应用的状态。与 Vuex 不同,Pinia 鼓励使用独立的 store 实例来管理状态,每个 store 实例都可以包含自己的状态、mutations、actions 等。

对比 Vuex

Pinia 起源于一次探索 Vuex 下一个迭代的实验,因此结合了 Vuex 5 核心团队讨论中的许多想法。最后,我们意识到 Pinia 已经实现了我们在 Vuex 5 中想要的大部分功能,所以决定将其作为新的推荐方案来代替 Vuex。

与 Vuex 相比,Pinia 不仅提供了一个更简单的 API,也提供了符合组合式 API 风格的 API,最重要的是,搭配 TypeScript 一起使用时有非常可靠的类型推断支持。

安装 Pinia

要开始使用 Pinia,首先确保你的项目已经安装了 Vue 3。

yarn add pinia
# 或者使用 npm
npm install pinia

安装 Pinia 持久化插件

pinia-plugin-persistedstate插件兼容 pinia^2.0.0,在使用之前请确保你已经安装 Pinia

yarn add pinia-plugin-persistedstate
# 或者使用 npm
npm install pinia-plugin-persistedstate
  • 与 vuex-persistedstate 相似的 API
  • 所有 Store 均可单独配置
  • 自定义 storage 和数据序列化
  • 恢复持久化数据前后的 hook
  • 每个 Store 具有丰富的配置
  • 兼容 Vue 2 和 3
  • 无任何外部依赖

配置 Pinia

// stores/index.ts
import { createPinia } from "pinia";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";

// piniaPersist(持久化)
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);

export default pinia;
// main.ts
import { createApp } from "vue";
import App from "./App.vue";
import pinia from "@/stores/index";

const app = createApp(App);
app.use(pinia).mount("#app");

创建 Store

在 Pinia 中,每个 store 都被视为一个独立的实例。我们可以通过 defineStore 函数来创建一个 store。

官方提供两种方法创建来Store,一种是选项式API、一种是函数式API。

  • Option Store
  • Setup Store

Option Store

与 Vue 的选项式 API 类似,我们也可以传入一个带有 stateactions 与 getters 属性的 Option 对象

import { defineStore } from "pinia";

// types
interface GlobalStoreType {
	token: string;
	userInfo: any;
}

// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。
// defineStore 调用后返回一个函数,调用该函数获得 Store 实体
export const useGlobalStore = defineStore({
	// id: 必须的,在所有 Store 中唯一
	id: "globalState",
	// state: 返回对象的函数
	state: (): GlobalStoreType => ({
		// token
		token: "",
		// userInfo
		userInfo: {},
	}),
	getters: {
		getToken(): string {
			return this.token;
		},
		getUserInfo(): any {
			return this.userInfo;
		}
	},
	actions: {
		// setToken
		setToken(token: string) {
			this.token = token;
		},
		// setUserInfo
		setUserInfo(userInfo: any) {
			this.userInfo = userInfo;
		}
	},
	persist: true,// 开启持久化
});

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

为方便上手使用,Option Store 应尽可能直观简单。

Setup Store

与 Vue 组合式 API 的 setup 函数 相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。

import { defineStore } from "pinia";
import { ref, computed } from "vue";

// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。
// defineStore 调用后返回一个函数,调用该函数获得 Store 实体
export const useGlobalStore = defineStore(
	// id: 必须的,在所有 Store 中唯一
	"globalState",
	() => {
		const token = ref("");
		const userInfo = ref({});

		const getToken = computed(() => token.value);
		const getUserInfo = computed(() => userInfo.value);

		const setToken = (val: string) => {
			token.value = val;
		};
		const setUserInfo = (val: any) => {
			userInfo.value = val;
		};
		return {
			token,
			userInfo,
			getToken,
			getUserInfo,
			setToken,
			setUserInfo
		};
	},
	{
		// 持久化
		persist: true
	}
);

在 Setup Store 中:

  • ref() 就是 state 属性
  • computed() 就是 getters
  • function() 就是 actions

注意,要让 pinia 正确识别 state,你必须在 setup store 中返回 state 的所有属性

使用 Store

虽然我们前面定义了一个 store,但在我们使用 <script setup> 调用 useStore()(或者使用 setup() 函数,像所有的组件那样) 之前,store 实例是不会被创建的:

<script setup>
import { useGlobalStore } from '@/stores/user'
// 可以在组件中的任意位置访问 `store` 变量 ✨
const globalStore = useGlobalStore()
</script>

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

从 Store 解构

<script setup>
import { useGlobalStore } from '@/stores/user'
const globalStore = useGlobalStore()

// ❌ 这将不起作用,因为它破坏了响应性
const { token, userInfo } = globalStore
token // 将始终是 ""
userInfo // 将始终是 {}

// ✅ 这样写是响应式的
// 💡 当然你也可以直接使用 `store.token`
const tokenValue = computed(() => store.token)
</script>

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

<script setup>
import { storeToRefs } from 'pinia'
import { useGlobalStore } from '@/stores/user'

const globalStore = useGlobalStore()
// `token` 和 `userInfo` 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
const { token, userInfo } = storeToRefs(globalStore)
// 作为 action 的 setToken 可以直接解构
const { setToken } = globalStore
</script>

Pinia 持久化配置

该插件的默认配置如下:

如果你不想使用默认的配置,那么你可以将一个对象传递给 Store 的 persist 属性来配置持久化。

import { defineStore } from 'pinia'

export const useStore = defineStore('main', {
  state: () => ({
    someState: '你好 pinia',
  }),
  persist: {
    // 在这里进行自定义配置
  },
})

详细配置项请查看官方文档

总结

本文介绍了如何使用 Pinia 管理 Vue 应用状态,并通过创建 store 和使用 store 在组件中共享状态。此外,我们还探讨了如何集成 Pinia 的持久化插件来确保应用状态的持久性,使用户在刷新页面后不会丢失状态。

Pinia 提供了一种简单、直观的方式来管理 Vue 应用的状态,它的强类型支持和独立的 store 实例使得状态管理变得更加灵活和可维护。希望本文能够帮助你更好地理解和使用 Pinia。