之前学习过vue的官方状态管理Vuex
,当时是基于Vue2的学习,现在已经Vue3了,那么继续学习吧。
当时Vuex
链接:快速学一遍vue的状态管理模式 -- Vuex_vue的action,view,state,store-CSDN博客
理论上Vue3也可以继续用Vuex
,不过Vue 官方现在推荐使用 Pinia 作为 Vue3 的默认状态管理库。Pinia 由 Vue 核心团队维护,提供了更简洁的 API 和更好的 TypeScript 支持。
为什么选择Pinia ?
- 更简单的 API:Pinia 移除了 Vuex 中的 mutations 概念,简化了状态更新流程
- TypeScript 支持:Pinia 从设计之初就考虑了 TypeScript,提供完整的类型推断
- 组合式 API 友好:与 Vue3 的组合式 API 风格高度契合
- 模块化设计:不再需要嵌套模块,每个 store 都是独立的
- 更轻量:Pinia 的体积比 Vuex 小很多
核心概念对比
概念 | Vuex | Pinia |
---|---|---|
状态 | state | state |
获取状态 | getters | getters |
修改状态 | mutations | 直接修改或 actions |
异步操作 | actions | actions |
模块系统 | modules | 独立的 stores |
TypeScript | 需要额外配置 | 开箱即用 |
聊天室项目实践
在聊天室项目当中,用户登陆后返回的id和name值在其他页面上也经常需要被用到,一直访问后台获取增加了网络开销,造成过度查询,因为这两个数据并不私密,可以考虑客户端存储。
考虑到兼顾安全性和 便捷性,采用pinia
和加密localStorage
的方式。
主流存储方案对比
存储位置 | 特点 | 安全性 | 持久性 | 适用场景 |
---|---|---|---|---|
Pinia/Vuex | 内存存储,响应式 | ❌ 刷新丢失 | 临时 | 非敏感数据,组件间共享 |
LocalStorage | 持久存储,5MB上限 | ⚠️ 明文存储 | 永久 | 需要长期保存的非敏感数据 |
SessionStorage | 标签页级存储 | ⚠️ 明文存储 | 标签页关闭失效 | 单次会话的临时数据 |
Cookie | 可设置HttpOnly | ✅ 最安全 | 可配置过期时间 | 敏感数据(需配合后端) |
VueUse useStorage | 响应式封装 | ⚠️ 依赖底层存储 | 可配置 | 需要响应式的持久化数据 |
安装
对pinia
和crypto
进行安装,安装命令:
yarn add pinia
yarn add crypto-js
pinia使用
修改main.js
文件以初始化pinia
:
- 添加了
Pinia
的导入语句import { createPinia } from 'pinia'
- 创建了
Pinia
实例const pinia = createPinia()
- 将
Pinia
挂载到Vue
应用app.use(pinia)
import { createPinia } from 'pinia';
const pinia = createPinia();
...
app.use(pinia);
在全局挂载Pinia后,创建一个stores
文件夹,用来存储保持状态和业务逻辑的实体:
- 在src文件夹下创建stores文件夹,然后再创建一个user.js文件,用来存储用户的信息
- 因为需要用到
localstorage
,这里的数据能在控制台看到,为了安全性考虑,使用crypto
进行加密,在utils文件夹下创建一个crypto.js
文件:
import CryptoJS from 'crypto-js';
const CRYPTO_KEY = import.meta.env.VITE_CRYPTO_KEY || 'kong-secure-key';
export const encrypt = (data) => {
if(!CRYPTO_KEY) {
throw new Error('加密密钥未配置');
}
return CryptoJS.AES.encrypt(data, CRYPTO_KEY).toString();
};
export const decrypt = (ciphertext) => {
if(!CRYPTO_KEY) {
throw new Error('加密密钥未配置');
}
return CryptoJS.AES.decrypt(ciphertext, CRYPTO_KEY).toString(CryptoJS.enc.Utf8);
};
- 最后采用Pinia + 加密LocalStorage 进行 🛡️安全存储,对于user的store进行定义,配置初始化设置用户信息,已经登出后重置状态清除本地存储用户信息,$reset是
pinia
提供的内置方法。
import { defineStore } from 'pinia';
import { decrypt, encrypt } from '@/utils';
export const useUserStore = defineStore('user', {
state: () => ({
userId: null,
userName: null,
}),
actions: {
setUser(user) {
this.userId = user.id;
this.userName = user.name;
// 加密后存LocalStorage
localStorage.setItem('chat-user', encrypt(JSON.stringify(user)));
},
logout() {
// 重置Pinia store的状态到初始值
this.$reset();
// 清除本地存储的用户信息
localStorage.removeItem('chat-user');
},
initialize() {
// 页面刷新时从LocalStorage恢复
const saved = localStorage.getItem('chat-user');
if (saved) {
try {
const data = JSON.parse(decrypt(saved));
this.userId = data.id;
this.userName = data.name;
} catch (e) {
this.logout();
}
}
}
},
})
-
经过上面的步骤,已经可以在其他页面中进行使用了:
- 导入
userUserStore
对象:import { useUserStore } from '@/stores/user';
- 创建一个实例:
const userStore = useUserStore();
- 获取userId:
userStore.userId
- 导入
-
这里页面刷新后,
userStore.userId
就获取不到了,所以需要调用上面定义时写的initialize
方法,可以在每个页面的初始化处调用一次,我选择在路由导航守卫中调用,这样在每个vue页面初始化时,不用再额外调用initialize
方法了:
router.beforeEach((to, from, next) => {
// 需要排除的路由名称列表
const excludeRoutes = ['login', 'register'];
if (!excludeRoutes.includes(to.name)) {
const userStore = useUserStore();
userStore.initialize();
}
next();
});
- 现在已经进行了登录,在本地中也已经存储了信息,获取store中userId也能获取到
对于常用的userId和username属性就已经实现了状态管理控制,如果需要管理一些其他变量,也可以在stores
文件夹中创建新的store,这就是类似于仓库一样。
其实pinia
的使用学习比较简单,上手速度极快,推荐在项目中直接使用
让我们边做边学吧, Let's Go!