前提条件
统一导出 Store
srcstoresindex.ts
/*
命名导出
语法分析 export { default as useTokenStore } from "./tokenStore"
default:指的是源文件中的默认导出,导出的是源文件的默认导出
as useTokenStore:将默认导出重命名为 useTokenStore
整体效果:这是一个命名导出
优点:
1、清晰的命名空间,避免命名冲突,每个 store 都有明确的名称,通过明确的命名区分
2、Tree-shaking友好,可以按需导入
3、类型安全,TypeScript 可以准确推断类型
4、导出方式统一明确,导出语句一致 export { default as xxx }
5、这是 Pinia store 的标准导出方式
*/
export { default as useTokenStore } from "./tokenStore";
export { default as useUserInfoStore } from "./userInfoStore";
export { default as useUserMenuStore } from "./userMenuStore";
export { default as useUserRouteStore } from "./userRouteStore";
export { default as useCapitalInfoStore } from "./capitalInfoStore";
export { default as useCapitalAllocateStore } from "./capitalAllocateStore";
export { default as useCapitalAllOptionStore } from "./capitalAllOptionStore";
export { default as useDepartmentStore } from "./departmentStore";
登出/退出系统 hook
srchooksuseLogout.ts
import { resetRoutes } from "@/router";
import {
useCapitalAllocateStore,
useCapitalInfoStore,
useTokenStore,
useUserInfoStore,
useUserMenuStore,
useUserRouteStore
} from "@/stores";
import { useRouter } from "vue-router";
/**
* 登出/退出系统,清除登录信息(pinia 存储的信息,包含 token,用户信息,用户菜单,用户路由,各业务Store),跳转到登录页面
*/
export const useLogout = () => {
// 令牌 token
const tokenStore = useTokenStore();
// 用户信息
const userInfoStore = useUserInfoStore();
// 用户菜单
const userMenuStore = useUserMenuStore();
// 用户路由
const userRouteStore = useUserRouteStore();
// 路由
const router = useRouter();
// 各业务Store
const businessStores = [useCapitalAllocateStore(), useCapitalInfoStore()];
const logout = () => {
try {
// 删除令牌 token
tokenStore.removeToken();
// 删除用户信息
userInfoStore.removeInfo();
// 删除用户菜单
userMenuStore.removeMenu();
// 删除用户路由
userRouteStore.removeRoute();
// 重置路由
resetRoutes();
// 重置各业务Store
for (const store of businessStores) {
store?.$reset();
}
} catch (error) {
console.log("登出/退出系统出错:", error);
} finally {
// 跳转到登录页面
router.push("/login");
}
};
return {
/** 登出/退出系统,清除登录信息(pinia 存储的信息,包含 token,用户信息,用户菜单,用户路由,各业务Store),跳转到登录页面 */
logout
};
};
export default useLogout;
解决方案
方案一:统一改用stores.xxx调用(推荐,完全自动化)
将原来所有使用命名导入的地方改为从 stores 对象获取:
import * as stores from "@/stores";
import { resetRoutes } from "@/router";
import { useRouter } from "vue-router";
export const useLogout = () => {
const router = useRouter();
const logout = () => {
try {
// 通过 stores 对象调用各个 store 构造函数
const tokenStore = stores.useTokenStore();
tokenStore.removeToken();
const userInfoStore = stores.useUserInfoStore();
userInfoStore.removeInfo();
const userMenuStore = stores.useUserMenuStore();
userMenuStore.removeMenu();
const userRouteStore = stores.useUserRouteStore();
userRouteStore.removeRoute();
resetRoutes();
// 遍历所有 store 并调用 $reset(自动包含所有业务 store)
Object.values(stores).forEach((useStore) => {
if (typeof useStore === 'function') {
const store = useStore();
if (store && typeof store.$reset === 'function') {
store.$reset();
}
}
});
} catch (error) {
console.log("登出/退出系统出错:", error);
} finally {
router.push("/login");
}
};
return { logout };
};
优点:新增任何 store 后无需修改登出逻辑,自动被遍历重置。
缺点:需要将原有命名导入全部替换为 stores.xxx 调用。
方案二:保留原有命名导入 + 额外导出构造函数数组(改动最小)
保持原有的命名导入方式不变,同时在 stores/index.ts 中额外导出一个包含所有构造函数引用的数组,供登出时遍历。
修改stores/index.ts:
// 原有命名导出保持不变
export { default as useTokenStore } from "./tokenStore";
export { default as useUserInfoStore } from "./userInfoStore";
export { default as useUserMenuStore } from "./userMenuStore";
export { default as useUserRouteStore } from "./userRouteStore";
export { default as useCapitalInfoStore } from "./capitalInfoStore";
export { default as useCapitalAllocateStore } from "./capitalAllocateStore";
export { default as useCapitalAllOptionStore } from "./capitalAllOptionStore";
export { default as useDepartmentStore } from "./departmentStore";
// 新增一个数组,包含所有业务 store 的构造函数(可根据需要只包含需要重置的)
export const businessStoreConstructors = [
useCapitalInfoStore,
useCapitalAllocateStore,
useCapitalAllOptionStore,
useDepartmentStore,
// 如果需要重置 tokenStore 等,也可以加入,但注意它们可能没有 $reset 或有其他副作用
];
在useLogout中使用:
import { resetRoutes } from "@/router";
import {
useTokenStore,
useUserInfoStore,
useUserMenuStore,
useUserRouteStore,
businessStoreConstructors, // 导入数组
} from "@/stores";
import { useRouter } from "vue-router";
export const useLogout = () => {
const router = useRouter();
const logout = () => {
try {
const tokenStore = useTokenStore();
tokenStore.removeToken();
const userInfoStore = useUserInfoStore();
userInfoStore.removeInfo();
const userMenuStore = useUserMenuStore();
userMenuStore.removeMenu();
const userRouteStore = useUserRouteStore();
userRouteStore.removeRoute();
resetRoutes();
// 遍历业务 store 数组并重置
businessStoreConstructors.forEach((useStore) => {
const store = useStore();
if (store && typeof store.$reset === 'function') {
store.$reset();
}
});
} catch (error) {
console.log("登出/退出系统出错:", error);
} finally {
router.push("/login");
}
};
return { logout };
};
优点:无需改动原有命名导入的调用代码,只需在 index.ts 中维护一个数组,新增 store 时手动添加一次即可。
缺点:仍需手动维护数组,但只集中在一处,比每次改登出逻辑方便。
选择建议
- 如果您希望代码最自动化(新增 store 后完全不用改任何代码),请用方案一。
- 如果您希望保留原有命名导入方式,且愿意在 stores/index.ts 中维护一个数组,用方案二。
两种方案均能解决您的问题。方案一需要一次性修改 useLogout 中的所有调用,方案二改动较小但需要维护数组。
不要使用store?.$reset();吗?
您当前代码中的判断 if (store && typeof store.$reset === “function”) 是安全的,等价于 store?.$reset() 的效果。可选链是语法糖,两者都能确保在 store 存在且 $reset 是函数时才调用,避免因方法不存在而报错。
您可以选择使用可选链使代码更简洁:
store?.$reset?.();
或者保留现有的显式判断,都完全正确。如果您希望代码更现代化,可以换成可选链,但不必强制修改。
resetRoutes();的位置合适吗?是不是放在最后更好些?
您问 resetRoutes() 的位置是否合适,是不是放在最后更好。这涉及到函数执行顺序的逻辑合理性,我们来分析一下:
当前顺序:
- 删除 token、用户信息、菜单、路由(这些操作清空了 Pinia store 中的用户相关状态)。
- 执行 resetRoutes()(重置动态路由,通常会将动态添加的路由从路由表中移除,只保留静态路由如 /login)。
- 遍历所有 store 并调用 $reset()(将每个业务 store 的状态重置为初始值)。
- 跳转到登录页。
分析:
- resetRoutes() 的目的是清理动态路由,确保用户登出后无法访问需要权限的页面。这个操作不依赖于任何 store 状态,只与 vue-router 实例有关。
- 调用 $reset() 重置业务 store 也不会影响路由表。
- 无论 resetRoutes() 放在遍历 store 之前还是之后,最终都能正常跳转登录页(因为登录页通常是静态路由,不会被 resetRoutes 移除)。
可能的小问题:
如果某个业务 store 的 $reset() 内部包含了与路由相关的操作(比如记录了当前路径等),那么先重置路由可能更安全。但一般 store 不会依赖路由对象,所以顺序几乎无关紧要。
建议:
从代码可读性和逻辑连贯性来看,将 resetRoutes() 放在所有状态清理之后、跳转之前会更清晰,因为它表示“清理完一切后,最后确保路由也干净,然后去登录”。即:
// 1. 删除用户相关 token、信息等
// 2. 重置所有业务 store
// 3. 重置动态路由
// 4. 跳转登录
但您当前的顺序也不会引起错误。如果您想调整,只需将 resetRoutes() 移到遍历 store 之后、router.push 之前即可。
结论:当前位置没有错误,但放在最后可能更符合“先清空数据,再清空路由,最后跳转”的逻辑顺序,增强代码可读性。您可以根据个人或团队风格决定是否调整。
如果用户不是正常退出登录,大部份用户都是直接关闭浏览器的,这样Store会重置吗?
直接关闭浏览器时,Pinia store 会被重置(即数据丢失),原因如下:
- Pinia store 默认存储在内存中:store 的数据保存在当前浏览器标签页的 JavaScript 运行时内存里,这些数据是临时的,只存在于当前页面会话期间。
- 关闭浏览器标签页或整个浏览器:对应的内存空间被释放,所有 store 中的数据都会消失。下次打开页面时,Pinia 会重新初始化 store,执行 state 函数返回初始值,因此 store 相当于被“重置”了。
但是,如果项目中使用了持久化插件(例如 pinia-plugin-persistedstate),数据会被保存到 localStorage 或 sessionStorage 中。这种情况下,关闭浏览器再打开,store 可能会从存储中恢复数据,不会重置。您的代码中没有引入持久化插件,所以无需担心。
总结:
- 正常退出(调用 logout)时,通过 $reset() 主动清空数据,是为了立即清除内存中的状态,防止切换账号时数据残留。
- 直接关闭浏览器,内存自动释放,自然达到重置效果,不需要额外处理。
如果后续项目中引入了持久化,则需考虑在登出时同时清除持久化存储(例如调用 localStorage.removeItem),否则关闭浏览器再打开仍会恢复旧数据。
到此这篇关于Vue3 pinia Store 重置到初始状态的两种方法的文章就介绍到这了,更多相关Vue3 pinia Store 重置内容请搜索IT俱乐部以前的文章或继续浏览下面的相关文章希望大家以后多多支持IT俱乐部!
