Vue 3项目中的路由管理和状态管理系统
核心概念理解
1. 整体架构关系
这两个文件构成了Vue应用的导航系统和状态管理系统:
- Router(路由):控制页面跳转和URL变化
- Store(状态):管理全局数据和用户状态
- 两者协同工作实现权限控制
2. 数据流向
用户操作 → 路由跳转 → 路由守卫检查 → 读取Store状态 → 决定是否允许访问
关键交互点分析
Router 与 Store 的协作
-
路由守卫中使用Store:
store.getters.token
- 检查登录状态store.getters.isAdmin
- 检查管理员权限
-
三层权限控制:
- 第一层:
requiresAuth
- 需要登录 - 第二层:
requiresAdmin
- 需要管理员权限 - 第三层:后端API验证(token验证)
- 第一层:
实际应用场景
用户登录流程:
- 用户在Login.vue输入账号密码
- 调用
store.dispatch('login', formData)
- Store发送API请求到后端
- 成功后存储token、user、role到Store和localStorage
- Router自动跳转到dashboard
- 后续访问带token请求后端API
用户登出流程:
-
用户点击登出按钮
-
调用
store.dispatch('logout')
-
清除所有用户状态
-
Router守卫检测到无token
-
自动重定向到登录页
-
安全性增强:
// 可以添加token过期检查
const isTokenExpired = (token) => {// 解析JWT token获取过期时间const payload = JSON.parse(atob(token.split('.')[1]));return Date.now() >= payload.exp * 1000;
};
- 用户体验优化:
// 记住用户要访问的页面
router.beforeEach((to, from, next) => {if (to.meta.requiresAuth && !token) {// 保存目标路径sessionStorage.setItem('redirectPath', to.fullPath);next('/login');}
});
- 错误处理完善:
// 在actions中添加更详细的错误处理
async login({ commit }, loginForm) {commit('SET_LOADING', true); // 添加加载状态try {// ... 登录逻辑} catch (error) {commit('SET_ERROR', error.message);throw error;} finally {commit('SET_LOADING', false);}
}
-
路由懒加载原理:
- 使用动态import()实现代码分割
- 只在需要时加载对应组件
- 减少首屏加载时间
-
Vuex持久化策略:
- localStorage:简单但有安全风险
- sessionStorage:关闭浏览器后失效
- Cookie:可设置httpOnly提高安全性
- IndexedDB:适合大量数据存储
-
路由守卫类型:
- 全局守卫:beforeEach、beforeResolve、afterEach
- 路由独享守卫:beforeEnter
- 组件内守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
这两个文件是Vue应用的基础设施
// ==========================================
// Vue Router 路由配置文件详解
// 文件路径: src/router/index.js
// ==========================================/*** 背景知识:* Vue Router 是 Vue.js 的官方路由管理器* 它用于构建单页面应用(SPA),实现页面间的导航* 主要功能包括:路由映射、嵌套路由、路由守卫、懒加载等*/// 1. 导入必要的依赖
import { createRouter, createWebHistory } from "vue-router"; // Vue Router 4.x 的核心函数
import store from "@/store"; // Vuex 状态管理,用于获取用户登录状态
import { ElMessage } from "element-plus"; // Element Plus UI库的消息提示组件/*** 2. 路由配置数组* 每个路由对象包含以下属性:* - path: URL路径* - name: 路由名称(用于编程式导航)* - component: 对应的Vue组件* - meta: 路由元信息(自定义数据)* - redirect: 重定向目标* - children: 子路由数组*/
const routes = [// 登录页路由{path: "/login", // 访问路径name: "Login", // 路由名称// 路由懒加载:只有访问该路由时才加载组件,优化首屏加载速度component: () => import("@/views/Login.vue"),meta: { requiresAuth: false // 元信息:标记该页面不需要登录验证},},// 主布局路由(包含所有需要登录的页面){path: "/", // 根路径name: "Layout",component: () => import("@/views/Layout.vue"), // 布局组件(通常包含导航栏、侧边栏等)redirect: "/dashboard", // 访问根路径时自动重定向到仪表盘meta: { requiresAuth: true // 需要登录验证},// 嵌套路由:这些页面都会渲染在Layout组件的<router-view>中children: [// 仪表盘页面{path: "dashboard", // 实际路径为 /dashboardname: "Dashboard",component: () => import("@/views/Dashboard.vue"),meta: { title: "仪表盘", // 页面标题(可用于动态设置浏览器标题)icon: "Odometer" // 图标名称(用于菜单显示)},},// 用户管理页面(仅管理员可访问){path: "users",name: "Users",component: () => import("@/views/UserManagement.vue"),meta: { title: "用户管理",icon: "User",requiresAdmin: true // 特殊权限标记:需要管理员权限},},// 个人信息页面{path: "profile",name: "Profile",component: () => import("@/views/Profile.vue"),meta: { title: "个人信息",icon: "UserFilled"},},],},// 404 通配符路由(必须放在最后){path: "/:pathMatch(.*)*", // Vue Router 4.x 的通配符语法redirect: "/login", // 所有未匹配的路径都重定向到登录页},
];/*** 3. 创建路由实例*/
const router = createRouter({// 使用 HTML5 History 模式(URL中没有#号,更美观)// 需要服务器配置支持,否则刷新页面会404history: createWebHistory(process.env.BASE_URL),routes, // 路由配置
});/*** 4. 全局前置守卫(Navigation Guards)* 在每次路由跳转前执行,用于权限验证* * 参数说明:* - to: 即将进入的目标路由对象* - from: 当前导航正要离开的路由对象* - next: 必须调用的函数,用于解析钩子*/
router.beforeEach((to, from, next) => {// 从 Vuex store 获取用户 token(判断是否登录)const token = store.getters.token;// 场景1:访问需要登录的页面,但用户未登录if (to.meta.requiresAuth && !token) {ElMessage.warning("请先登录"); // 显示提示消息next("/login"); // 重定向到登录页} // 场景2:访问需要管理员权限的页面,但用户不是管理员else if (to.meta.requiresAdmin && !store.getters.isAdmin) {ElMessage.error("权限不足");next("/dashboard"); // 重定向到仪表盘} // 场景3:已登录用户访问登录页(避免重复登录)else if (to.path === "/login" && token) {next("/dashboard"); // 直接跳转到仪表盘} // 场景4:正常访问else {next(); // 放行,继续导航}
});/*** 5. 导出路由实例* 在 main.js 中需要引入并注册到 Vue 应用中*/
export default router;/*** 使用示例:* * 1. 在组件中进行路由跳转:* this.$router.push('/users') // 编程式导航* <router-link to="/profile">个人信息</router-link> // 声明式导航* * 2. 获取路由参数:* this.$route.params.id // 获取动态路由参数* this.$route.query.name // 获取查询参数* * 3. 路由守卫的执行顺序:* 全局前置守卫 → 路由独享守卫 → 组件内守卫 → 全局后置钩子*/
// ==========================================
// Vuex Store 状态管理配置详解
// 文件路径: src/store/index.js
// ==========================================/*** 背景知识:* Vuex 是 Vue.js 的状态管理库,用于管理应用的全局状态* 核心概念:State(状态)、Getters(计算属性)、Mutations(同步修改)、Actions(异步操作)* 数据流:组件 → dispatch Action → commit Mutation → 修改 State → 响应式更新组件*/// 1. 导入必要的依赖
import { createStore } from "vuex"; // Vuex 4.x 的创建函数(适配 Vue 3)
import { login, logout } from "@/api/user"; // API 接口函数(与后端通信)/*** 2. 创建 Vuex Store 实例* Store 是一个单一状态树,包含应用的所有状态*/
const store = createStore({/*** 3. State - 状态定义* 存储应用的数据,相当于组件的 data* 初始值从 localStorage 读取,实现持久化存储*/state: {// 用户认证令牌(JWT Token)// 用于后端API请求的身份验证token: localStorage.getItem("token") || "",// 用户信息对象// 包含用户名、ID、头像等基本信息user: JSON.parse(localStorage.getItem("user") || "{}"),// 用户角色// 用于前端权限控制(如:admin、user、guest)role: localStorage.getItem("role") || "",},/*** 4. Getters - 计算属性* 类似组件的 computed,用于派生状态* 可以对 state 进行处理后返回,具有缓存特性*/getters: {// 获取 tokentoken: (state) => state.token,// 获取用户信息user: (state) => state.user,// 获取用户角色role: (state) => state.role,// 判断是否为管理员(派生状态)// 这是一个计算属性,根据 role 动态计算isAdmin: (state) => state.role === "admin",// 可以添加更多 getters,例如:// isLoggedIn: (state) => !!state.token, // 是否已登录// userName: (state) => state.user.name || '游客', // 用户名},/*** 5. Mutations - 同步状态修改* 唯一能修改 state 的方法,必须是同步函数* 通过 commit 触发:store.commit('SET_TOKEN', value)* * 命名约定:通常使用大写常量风格*/mutations: {// 设置 tokenSET_TOKEN(state, token) {state.token = token; // 更新内存中的状态localStorage.setItem("token", token); // 持久化到本地存储},// 设置用户信息SET_USER(state, user) {state.user = user;// JSON.stringify 将对象序列化为字符串存储localStorage.setItem("user", JSON.stringify(user));},// 设置用户角色SET_ROLE(state, role) {state.role = role;localStorage.setItem("role", role);},// 清除用户信息(用于登出)CLEAR_USER(state) {// 重置所有用户相关状态state.token = "";state.user = {};state.role = "";// 清除本地存储localStorage.removeItem("token");localStorage.removeItem("user");localStorage.removeItem("role");},},/*** 6. Actions - 异步操作* 用于处理异步逻辑(如 API 请求)* 通过 dispatch 触发:store.dispatch('login', payload)* 可以包含任意异步操作,最终通过 commit 调用 mutation*/actions: {/*** 登录操作* @param {Object} context - 包含 commit、dispatch、state 等* @param {Object} loginForm - 登录表单数据(用户名、密码)*/async login({ commit }, loginForm) {try {// 1. 调用登录 APIconst response = await login(loginForm);// 2. 解构响应数据// 假设后端返回格式:{ data: { token, user, role } }const { token, user, role } = response.data;// 3. 通过 mutations 更新状态commit("SET_TOKEN", token);commit("SET_USER", user);commit("SET_ROLE", role);// 4. 返回响应(供组件使用)return response;} catch (error) {// 错误处理console.error('登录失败:', error);// 可以添加更详细的错误处理// 例如:根据错误码显示不同提示if (error.response?.status === 401) {throw new Error('用户名或密码错误');} else if (error.response?.status === 500) {throw new Error('服务器错误,请稍后重试');}// 继续抛出错误,让调用方处理throw error;}},/*** 登出操作* @param {Object} context - Vuex context 对象*/async logout({ commit }) {try {// 1. 调用登出 API(通知后端清除 session 或 token)await logout();} catch (error) {// 即使 API 调用失败,也要清除本地状态console.error("Logout API error:", error);} finally {// 2. 无论成功与否,都清除本地用户信息commit("CLEAR_USER");}},// 可以添加更多 actions,例如:// async updateProfile({ commit }, profileData) { ... }// async refreshToken({ commit }) { ... }},/*** 7. Modules(模块化)- 这里未使用,但是重要概念* 当应用变大时,可以将 store 分割成模块* * modules: {* user: userModule,* product: productModule,* cart: cartModule* }*/
});// 导出 store 实例
export default store;/*** 使用示例:* * 1. 在组件中使用 state:* computed: {* token() { return this.$store.state.token }* }* * 2. 使用 getters:* computed: {* isAdmin() { return this.$store.getters.isAdmin }* }* * 3. 调用 mutations:* this.$store.commit('SET_TOKEN', 'new-token')* * 4. 调用 actions:* this.$store.dispatch('login', { username, password })* * 5. 使用辅助函数(更简洁):* import { mapState, mapGetters, mapActions } from 'vuex'* computed: {* ...mapState(['token', 'user']),* ...mapGetters(['isAdmin'])* },* methods: {* ...mapActions(['login', 'logout'])* }* * 6. 在路由守卫中使用(如第一个文件所示):* const token = store.getters.token*//*** 注意事项:* * 1. LocalStorage 的局限性:* - 存储容量有限(通常 5-10MB)* - 只能存储字符串* - 同步操作,可能阻塞主线程* - 不适合存储敏感信息* * 2. Token 安全性:* - 考虑使用 httpOnly Cookie 存储 token* - 实现 token 刷新机制* - 设置合理的过期时间* * 3. 性能优化:* - 避免在 state 中存储大量数据* - 使用 getters 缓存计算结果* - 合理使用模块化分割 store*/