主题
Pinia 状态管理
Pinia 是 Vue 官方推荐的状态管理库,用 state、getters、actions 三个概念覆盖全局状态、派生数据与修改逻辑。本文基于 Pinia 2.x 与 Vue 3 项目,依次说明安装、store 定义、组件内读取、异步 action、store 相互引用、在路由守卫中获取以及数据持久化。
参考文档:Pinia 官方文档(原始笔记记录的镜像地址为 pinia.web3doc.top)。
目录
一、特点
- 完整的 TypeScript 支持,
state、getters、actions都有类型推导。 - 三大核心概念:
state:存储的值;getters:计算属性;actions:改变值的方法,同时支持同步与异步。
- 与 Vuex 相比去掉了
mutations(actions也能同步修改)和modules(store 之间直接相互引用)。
Vuex 的概念在 Pinia 中的对应关系:
| Vuex | Pinia | 说明 |
|---|---|---|
state | state | 存储的值 |
getters | getters | 计算属性 |
mutations | 无 | 由 actions 承担,支持同步修改 |
actions | actions | 支持异步 |
modules | 无 | 每个 store 独立定义,可相互引用 |
二、安装
bash
yarn add pinianpm 项目对应 npm install pinia。
三、接入应用
1. 创建 pinia 实例
新建 store/index.ts:
ts
import { createPinia } from "pinia";
const store = createPinia();
export default store;2. 在入口文件中注册
在 main.ts 中通过 app.use(store) 挂载,让组件树在挂载前就具备 store:
ts
import { createApp } from "vue";
import App from "./App.vue";
import store from "./store";
const app = createApp(App);
app.use(store);
app.mount("#app");四、定义 store
新建 store/user.ts。defineStore 的第一个参数是 store 的 id,必需且值唯一;state 返回一个函数,避免多个组件实例共享同一个对象造成作用域污染。
ts
import { defineStore } from "pinia";
export const useUserStore = defineStore("user", {
// state 返回一个函数,防止作用域污染
state: () => {
return {
userInfo: {
name: "zhangsan",
age: 23,
},
token: "S1",
};
},
getters: {
newName: (state) => state.userInfo.name + "vip",
},
actions: {
// 更新整个对象
updateUserInfo(userInfo: { name: string; age: number }) {
this.userInfo = userInfo;
},
// 更新对象中某个属性
updateAge(age: number) {
this.userInfo.age = age;
},
// 更新基础数据类型
updateToken(token: string) {
this.token = token;
},
},
});五、在组件中使用
useUserStore() 返回的 store 是一个用 reactive 包装的对象。直接解构读取 state 会失去响应式,因此需要用 storeToRefs 为每个响应式属性创建 ref 引用,解构出来的值通过 .value 读取。
storeToRefs 会跳过所有的 action 属性,action 直接从 store 上解构即可。
1. 完整示例
vue
<template>
<div>
<div>姓名:{{ userInfo.name }} 年龄:{{ userInfo.age }}</div>
<div>token:{{ token }}</div>
<div>getter 值:{{ newName }}</div>
<button @click="handleUser">更新用户</button>
<button @click="handleAge">更新年龄</button>
<button @click="handleToken">更新 token</button>
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { useUserStore } from "@/store/user";
const userStore = useUserStore();
// storeToRefs 会跳过所有的 action 属性
const { userInfo, token, newName } = storeToRefs(userStore);
// action 属性直接解构
const { updateUserInfo, updateAge, updateToken } = userStore;
const handleUser = () => {
updateUserInfo({ name: "lisi", age: 24 });
};
const handleAge = () => {
// userInfo 是一个 ref 响应式引用,需通过 .value 取值
updateAge(userInfo.value.age + 1);
};
const handleToken = () => {
updateToken("23234");
};
</script>六、异步 actions
除同步方法外,action 也可以写成 async 方法。新建 store/list.ts:
ts
import { defineStore } from "pinia";
const getData = () => {
return new Promise<number>((resolve) => {
setTimeout(() => {
resolve(Math.random() * 100);
}, 200);
});
};
export const useListStore = defineStore("list", {
state: () => {
return {
list: [] as number[],
};
},
actions: {
async updateList() {
try {
const data = await getData();
this.list.push(data);
} catch {
// 请求失败时不做处理
}
},
},
});七、store 之间相互引用
每个 store 都可以看成一个 hook,相互引用就是在 action 中调用另一个 store 的 hook。新建 store/userSex.ts:
ts
import { defineStore } from "pinia";
import { useUserStore } from "./user";
enum Sex {
man = "男人",
woman = "女人",
}
export const userSexStore = defineStore("user2", {
state: () => {
return {
sex: Sex.man,
};
},
actions: {
updateSex() {
const userStore = useUserStore(); // 引用其他 store
if (userStore.userInfo.name !== "zhangsan") this.sex = Sex.woman;
},
},
});注意:store 的 id 必须全局唯一。这里用
user2与store/user.ts中的user区分,避免 id 冲突。
八、在路由守卫中使用
在 router.beforeEach 中可以直接调用 store 的 hook,前提是 Pinia 已被注册。路由器是在其被安装之后才开始导航的,此时 app.use(store) 已经执行完毕:
ts
import { useUserStore } from "@/store/user";
router.beforeEach((to, from) => {
// 路由器在安装之后才开始导航,此时 Pinia 已经安装
const userStore = useUserStore();
if (!userStore.token && to.path !== "/login") {
return "/login";
}
});九、数据持久化
1. 安装插件
bash
yarn add pinia-plugin-persistedstate2. 注册插件
修改 store/index.ts,在 pinia 实例上使用插件:
ts
import { createPinia } from "pinia";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
const store = createPinia();
store.use(piniaPluginPersistedstate); // 使用持久化插件
export default store;3. 在 store 模块中启用持久化
在 store/user.ts 的 defineStore 选项中添加 persist 配置项:
ts
import { defineStore } from "pinia";
export const useUserStore = defineStore("user", {
state: () => ({
userInfo: { name: "zhangsan", age: 23 },
token: "S1",
}),
getters: {
newName: (state) => state.userInfo.name + "vip",
},
actions: {
// 与「四、定义 store」中的 actions 相同
},
// 开始数据持久化
persist: true,
});启用后,每次更新 state 都会默认写入 localStorage。
4. 修改 key 与存储位置
默认写入 localStorage 的 key 就是 store 的 id(此处为 user)。把 persist: true 换成对象即可修改:
ts
persist: {
key: 'storekey', // 修改存储的键名,默认为当前 store 的 id
storage: sessionStorage, // 存储位置改为 sessionStorage
},注意:插件要求
storage是同步存储,localStorage与sessionStorage都满足,异步存储(如 IndexedDB 封装)不行。
5. 自定义要持久化的字段
默认持久化 state 的所有字段,可以用点符号路径数组只挑选需要缓存的字段:
ts
persist: {
pick: ['userInfo.name'], // 只持久化 userInfo 的 name
},需要反向排除字段时用 omit。
注意:
pick是 pinia-plugin-persistedstate v4 的选项名,v3 中同名选项为paths。升级插件后需同步改配置。