Vue3下使用pinia store
首先感谢 小满zs大神的文章及其 B站课程。收益匪浅,强烈推荐。下面的pinia自动持久化插件,就是根据他的 视频编写的。在此声明本章代码并非完全原创。
插件为了通用性,有些繁琐,可以根据自己需求改写。为了与Vuex方法对比,内容参照 Vue3下使用Vuex(store)实现响应式全局变量。
pinia较Vuex更容易上手,且实现部分更灵活。
本文内容在我实际使用过程中的功能修改、bug修复会随时同步更新。更希望大家评论区多指教、讨论。
1 安装
项目路径下,终端内执行
vue add pinia -S
或者
yarn install pinia -S
2 编写pinia自动持久化插件
持久化存储器支持local,session,cookie
三种方式。
- 为了满足不同store实例可以自定义不同持久化存储器类型,需要在实例属性中定义存储器类型,为此定义了
StoreCommonData
类型,作为所有store实例的“基类”。 - 持久化参数中
keyPrefix
-key值前缀的意义是为统一管理本系统定义存储器内属性。
插件文件 在/src/utils/store
目录下。
2.1 ts类型定义
/src/utils/store/index.d.ts文件
// store存储器类型
export type StoreType = 'local' | 'session' | 'cookie';
/**
* store基本/共有数据
*/
export type StoreCommonData = {
// 存储类型
readonly storageType: StoreType
}
// pinia存储器持久化插件配置
export type PiniaStorageOptions = {
//key 前缀 该值默认值:__piniaDefaultKeyPrefix__
keyPrefix?: string,
//默认存储类型,如果store未设置,则使用默认存储类型。该值默认值:__defaultStoreType__
defaultStoreType?: StoreType,
//需要管理的store key(不含前缀),未定义时管理所有store
manageStoreKeys?: string[]
//不需要管理的store key(不含前缀),未定义时管理manageStoreKeys定义内容,
// 有冲突时优先使用unManageStoreKeys定义内容
unManageStoreKeys?: string[]
}
2.2 pinia自动持久化插件
/src/utils/store/index.ts文件
/**
* pinia持久化存储
* 更新日志:
* 2024/6/4 添加key的加前缀和去前缀方法;移除指定存储器类型的数据方法,添加不移除keys的参数
* @author MuYi
* @date 2024/5/24
* @version 1.0
*/
import {PiniaPluginContext} from 'pinia';
import {toRaw} from "vue";
import {logClientInfo} from "~/api/system/sys";
import {COOKIE_EXPIRES_HOURS} from "~/config/env";
import type {PiniaStorageOptions, StoreCommonData, StoreType} from "./index.d"
const __piniaDefaultKeyPrefix__: string = 'MUYI_';
const __defaultStoreType__ = 'local';
let PINIA_DEFAULT_KEY_PREFIX: string = __piniaDefaultKeyPrefix__;
export const piniaStorage = (storageOptions?: PiniaStorageOptions) => {
PINIA_DEFAULT_KEY_PREFIX = `${storageOptions?.keyPrefix ?? __piniaDefaultKeyPrefix__}`
return (context: PiniaPluginContext) => {
const {store} = context;
let key = store.$id;
if (storageOptions?.unManageStoreKeys && storageOptions.unManageStoreKeys.includes(key)) return;
if (storageOptions?.manageStoreKeys && !storageOptions.manageStoreKeys.includes(key)) return;
const keyWithPrefix = getKeyWithPrefix(key);
const storageType = getStorageType(toRaw(store.$state) as StoreCommonData, storageOptions);
const data = getStorage(keyWithPrefix) as StoreCommonData;
store.$subscribe(() => {
try {
setStorage(keyWithPrefix, toRaw(store.$state), storageType);
} catch (error) {
logClientInfo('Storage set operation failed:' + error);
}
})
return {...data}
}
}
/**
* 删除local、session、cookie存储。未指定key时,删除所有PINIA_DEFAULT_KEY_PREFIX为前缀的key存储。
* @param keysWithOutPrefix 存储key,不含前缀。
*/
export const removeStorage = (keysWithOutPrefix?: string[]) => {
if (!keysWithOutPrefix || keysWithOutPrefix.length === 0) {
const keysToRemove = [
...getLocalStorageKeys(),
...getSessionStorageKeys(),
...getCookieStorageKeys()
];
keysToRemove.forEach(key => {
try {
localStorage.removeItem(key);
sessionStorage.removeItem(key);
setCookieExpires(key);
} catch (error) {
logClientInfo('Storage remove [‘+key+’] operation failed:' + error);
}
});
return;
}
keysWithOutPrefix.forEach(key => {
let k = getKeyWithPrefix(key)
try {
localStorage.removeItem(k);
sessionStorage.removeItem(k);
setCookieExpires(k);
} catch (error) {
logClientInfo('Storage remove [‘+k+’] operation failed:' + error);
}
})
}
/**
* 移除指定类型的存储(带有PINIA_DEFAULT_KEY_PREFIX前缀的所有key)
* @param type 存储器类型
* @param unRemoveKeysWithOutPrefix 不删除的key(不包含PINIA_DEFAULT_KEY_PREFIX前缀)列表
*/
export const removeStorageByType = (type: StoreType, unRemoveKeysWithOutPrefix?: string[]) => {
switch (type) {
case 'local':
removeSpeacialKeys(getLocalStorageKeys(), unRemoveKeysWithOutPrefix).forEach(key => {
try {
localStorage.removeItem(key);
} catch (error) {
logClientInfo('Local Storage remove [‘+key+’] operation failed:' + error);
}
})
break;
case 'session':
removeSpeacialKeys(getSessionStorageKeys(), unRemoveKeysWithOutPrefix).forEach(key => {
try {
sessionStorage.removeItem(key);
} catch (error) {
logClientInfo('Session Storage remove [‘+key+’] operation failed:' + error);
}
});
break;
case 'cookie':
removeSpeacialKeys(getCookieStorageKeys(), unRemoveKeysWithOutPrefix).forEach(key => {
setCookieExpires(key);
});
break;
}
}
/**
* 使cookie过期
* @param key key
*/
const setCookieExpires = (key: string) => {
// 获取当前时间并设置为过去的时间(确保立即过期)
const now = new Date();
now.setTime(now.getTime() - (1000 * 60 * 60 * 24));
const expires = now.toUTCString();
document.cookie = `${key}=; expires=${expires}; path=/;`
}
/**
* 获得本地存储的所有具有PINIA_DEFAULT_KEY_PREFIX前缀的key
*/
const getLocalStorageKeys = () => {
return Object.keys(localStorage).filter(key => key.startsWith(PINIA_DEFAULT_KEY_PREFIX));
}
const removeSpeacialKeys = (sourceList: string[], unIncludeKeysWithOutPrefix?: string[]) => {
if (sourceList && sourceList.length > 0 && unIncludeKeysWithOutPrefix && unIncludeKeysWithOutPrefix.length > 0) {
return sourceList.filter(key => !unIncludeKeysWithOutPrefix.includes(getKeyWithOutPrefix(key)));
} else return sourceList;
}
const getKeyWithPrefix = (keyWithOutPrefix: string) => {
return `${PINIA_DEFAULT_KEY_PREFIX}${keyWithOutPrefix}`
}
const getKeyWithOutPrefix = (getKeyWithPrefix: string) => {
return getKeyWithPrefix.replace(PINIA_DEFAULT_KEY_PREFIX, '')
}
/**
* 获得session存储的所有具有PINIA_DEFAULT_KEY_PREFIX前缀的key
*/
const getSessionStorageKeys = () => {
return Object.keys(sessionStorage).filter(key => key.startsWith(PINIA_DEFAULT_KEY_PREFIX));
}
/**
* 获得cookie存储的所有具有PINIA_DEFAULT_KEY_PREFIX前缀的key
*/
const getCookieStorageKeys = () => {
return Object.keys(document.cookie).filter(key => key.startsWith(PINIA_DEFAULT_KEY_PREFIX));
}
const getStorageType = (value: StoreCommonData, storageOptions?: PiniaStorageOptions) => {
return value.storageType ?? (storageOptions?.defaultStoreType ?? __defaultStoreType__);
}
const setStorage = (itemKey: string, value: any, storageType?: StoreType) => {
let v = JSON.stringify(value);
switch (storageType) {
case 'local':
localStorage.setItem(itemKey, v)
break;
case 'session':
sessionStorage.setItem(itemKey, v)
break;
case 'cookie':
const expirationDate = new Date(Date.now() + COOKIE_EXPIRES_HOURS * 60 * 60 * 1000); // 过期时间,单位为毫秒
document.cookie = `${itemKey}=${v}; expires=${expirationDate.toUTCString()}; path=/;`;
break;
}
}
const getStorage = (itemKey: string, storageType: StoreType = __defaultStoreType__): {} => {
let result: string | null = null;
switch (storageType) {
case 'local':
result = localStorage.getItem(itemKey) as string
break;
case 'session':
result = sessionStorage.getItem(itemKey) as string
break;
case 'cookie':
let cookies = document.cookie.split('; ');
for (let i = 0; i < cookies.length; i++) {
let [name,] = cookies[i].split('=');
if (name === itemKey) {
result = decodeURIComponent(name.split('=')[1]);
break;
}
}
break;
}
return result ? JSON.parse(result) : {}
}
- 其中的使用的
COOKIE_EXPIRES_HOURS
内容如下
// cookie过期时间。单位小时。默认2天
export const COOKIE_EXPIRES_HOURS = 24 * 2; // (以小时计)
logClientInfo
方法向后台传送错误日志,可以删除该方法。不影响插件功能removeStorage
方法用于在系统注销或需要时,删除存储器内容
2.3 加载引用插件
main.ts文件中添加如下代码。
import {piniaStorage} from "~/utils/store";
import {Stores} from "~/stores/type";
pinia.use(piniaStorage({
manageStoreKeys: [Stores.appInfo]
}))
app.use(pinia);
a. app是vue的实例
b. piniaStorage传入的参数按自己需求填写,也可以使用默认值,即不输入参数。
3 vue store实例
3.1 目录及文件结构
按惯例所有store实例应存储在stores目录下,其中具体方案按自己喜好。
3.2 store实例命名统一管理
为了统一管理store实例,建议编写如下type.ts文件
/**
* 本系统包含的状态管理类型
* @author MuYi
* @date 2024/5/24
* @version 1.0
*/
export enum Stores{
//app注册及版权信息
appInfo='appInfo',
//用户信息
userInfo='userInfo',
//主题信息
theme='theme'
}
3.3 编写store实例
/**
* app注册及版权信息,同步在session中,退出则被清除
*@author MuYi
*@date 2022/3/21 8:58
*@version 1.0
*/
import {defineStore} from 'pinia';
import {useUserOperateStore} from "./userOperate";
import type {StoreCommonData} from "~/utils/store/index.d";
import {Stores} from "~/stores/type";
/**
* app信息数据结构
*/
export type StateAppInfo = StoreCommonData & {
registerCompany: string,
appName: string,
appTitle: string,
appTitleEng: string,
version: string,
copyright: string,
copyrightYear: string,
author: string,
}
const getDefaultValue = (): StateAppInfo => {
return {
registerCompany: "请联系xxxxx注册您的公司",
appName: "MUYI",
appTitle: "WinTown Software",
appTitleEng: "WinTown Software",
version: "非合法版",
copyright: "WinTown Software studio All rights reserved.",
copyrightYear: "©请使用正版",
author: "Mobile:xxxx; Email:xxx@qq.com",
storageType: 'local'
}
}
export const useAppInfoStore = defineStore(Stores.appInfo, {
state: (): StateAppInfo => {
return getDefaultValue();
},
getters: {
docTitle: (state: StateAppInfo) => {
let title ;
try {
const userOperateStore = useUserOperateStore();
title = userOperateStore.activeTagValue.title;
} catch {
title = "";
}
return state.appName + "-" + title;
},
},
actions: {
/**
* 保存/设置app信息
* @param appInfo{StateAppInfo}
*/
saveAppInfo(appInfo: StateAppInfo) {
if (appInfo && appInfo.registerCompany) {
this.$state = appInfo;
} else {
this.$reset();
}
},
/**
* 清空程序信息
*/
clearAppInfo() {
this.$reset();
},
}
});
注意:
- 实例属性类型声明时,需引用
StoreCommonData
storageType
属性用来定义存储器类型
3.3 使用store实例
示例1:
import {StateAppInfo, useAppInfoStore} from "~/stores/appInfo"
/**
* 获取app软件信息
* @return {Promise<boolean>}
*/
const getAppInfo = async () => {
const appInfoStore = useAppInfoStore();
try {
let url = "/system/getAppInfo";
let result = await request.get<StateAppInfo>(url)
if (result.data && result.success) {
appInfoStore.saveAppInfo(result.data);
return true;
}
return false;
} catch (e) {
appInfoStore.clearAppInfo();
logClientInfo("获取AppInfo出错。" + e)
return false;
}
}
示例2:
/**
* 清除用户及程序必要本地信息
*/
export function clearStorageInfo() {
removeStorageByType('local',[Stores.userOperate]);
removeStorageByType('session');
}