路由导航守卫

全局守卫(作用于所有路由,并支持注册多个)

router.beforeEach

  1. 全局前置守卫可以使用 router.beforeEach 注册,跳转前触发,常用于登录验证等需要跳转前告知的情况;
  2. 当导航触发时,全局前置守卫按照创建顺序调用;守卫是异步解析执行,导航会等待所有守卫解析完成后才会继续;
  3. 案例:
    // 登录验证守卫
    router.beforeEach(async (to: RouteLocationNormalized) => {
      const authStore = useAuthStore();
      const isAuthenticated = authStore.isAuthenticated;
    
      // 需要认证但未登录
      if (to.meta.requiresAuth && !isAuthenticated) {
        return {
          path: "/login",
          query: { redirect: to.fullPath },
        };
      }
    
      // 已登录但访问登录/注册页
      if ((to.path === "/login" || to.path === "/register") && isAuthenticated) {
        return "/";
      }
    });
    
    // 权限控制守卫
    router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
        const authStore = useAuthStore();
        const requiredRoles = to.meta.roles as UserRole[];
    
        if (requiredRoles) {
          const hasPermission = requiredRoles.some((role) =>
            authStore.userRoles.includes(role)
          );
    
          if (!hasPermission) {
            next({ name: "forbidden" });
            return;
          }
        }
    
        next();
      }
    );
    

router.beforeResolve

  1. 全局解析守卫在 beforeEach 之后,在导航被确认之前调用,在所有组件内守卫和异步路由组件解析完成后执行,是进行最后数据获取或验证的理想位置;

  2. 案例:

    TypeScript
    TypeScript
    // 1. 页面访问权限最终检查
    router.beforeResolve((to, from) => {
      const authStore = useAuthStore();
    
      // 在最终确认前再次检查权限
      if (to.meta.requiresSpecialPermission && !authStore.checkSpecialPermission()) {
        return { name: "unauthorized" };
      }
    });
    
    // 2. 预加载关键资源
    router.beforeResolve(async (to) => {
      // 预加载下一页可能需要的资源
      if (to.name === "product-detail") {
        await preloadProductImages(to.params.id as string);
      }
    
      if (to.name === "dashboard") {
        await preloadDashboardCharts();
      }
    });
    

router.afterEach

  1. afterEach 在导航完成后调用,适合执行不需要影响导航的后续操作,如分析跟踪、页面滚动等;

  2. 案例:

    TypeScript
    TypeScript
    TypeScript
    // 1. 页面访问统计
    router.afterEach((to, from, failure) => {
      if (!failure) {
        // 发送页面浏览统计
        trackPageView(to.fullPath);
    
        // 或使用分析工具
        if (window.gtag) {
          gtag("event", "page_view", { page_path: to.fullPath, page_title: to.meta.title || "Untitled" });
        }
      }
    });
    
    function trackPageView(path: string) {
      // 实现你的统计逻辑
      console.log("Page viewed:", path);
    }
    
    // 2. 页面标题管理
    router.afterEach((to) => {
      // 设置页面标题
      const appName = "My Awesome App";
      document.title = to.meta.title ? `${to.meta.title} | ${appName}` : appName;
    
      // 更新meta描述
      updateMetaDescription(to.meta.description);
    });
    
    function updateMetaDescription(description?: string) {
      const metaDesc = document.querySelector('meta[name="description"]');
      if (metaDesc) {
        metaDesc.setAttribute("content", description || "默认描述");
      }
    }
    
    // 3. 滚动行为控制
    router.afterEach((to) => {
      // 滚动到顶部(如果不需要特殊滚动行为)
      if (to.meta.scrollToTop !== false) {
        window.scrollTo(0, 0);
      }
    
      // 或者更精细的控制
      if (to.hash) {
        const el = document.querySelector(to.hash);
        if (el) {
          el.scrollIntoView({ behavior: "smooth" });
        }
      }
    });
    

路由独享守卫(只作用于特定路由)

beforeEnter

  1. 在路由配置上直接定义 beforeEnter 守卫,和全局的 beforeEnter 完全相同,如果都设置则在 beforeEnter 之后紧随执行;

  2. 案例:

    TypeScript
    TypeScript
    // 1. 管理员路由守卫
    const routes: RouteRecordRaw[] = [
      {
        path: "/admin",
        name: "admin",
        component: () => import("@/views/AdminView.vue"),
        meta: { requiresAuth: true },
        beforeEnter: (to, from) => {
          const authStore = useAuthStore();
          if (authStore.userRole !== "admin") {
            return { name: "forbidden" };
          }
        },
      },
    ];
    
    // 2. 动态参数验证
    const routes: RouteRecordRaw[] = [
      {
        path: "/user/:id(\\d+)", // 只匹配数字ID
        name: "user",
        component: () => import("@/views/UserView.vue"),
        beforeEnter: (to) => {
          if (!/^\d+$/.test(to.params.id as string)) {
            return { name: "not-found" };
          }
        },
      },
    ];
    

组件内守卫(在路由组件内定义)

beforeRouteEnter

  1. 在渲染该组件的对应路由被验证前调用,此时组件还未创建;

  2. 案例:

    // 数据预加载
    onBeforeRouteEnter(async (to, from, next) => {
      try {
        await fetchUser(to.params.id as string);
        next();
      } catch (error) {
        next({ name: "error" });
      }
    });
    

beforeRouteUpdate

  1. 在当前路由改变,但该组件被复用时调用 (例如对于带有动态参数的路径 /foo/:id,当从 /foo/1 跳转到 /foo/2 时会被调用)

  2. 案例:

    onBeforeRouteUpdate(async (to, from) => {
      try {
        await fetchUser(to.params.id as string);
      } catch (error) {
        // 处理错误
      }
    });
    

beforeRouteLeave

  1. 在导航离开前调用;

  2. 案例:

    const formData = ref(/* ... */);
    const isDirty = ref(false);
    
    onBeforeRouteLeave((to, from) => {
      if (isDirty.value) {
        const confirmLeave = window.confirm("您有未保存的更改,确定要离开吗?");
        if (!confirmLeave) return false;
      }
    });
    

return、next() 控制路由行为

next()(传统方式)

  1. 这是 Vue Router 34 都支持的标准方式,必须显式调用 next(),否则导航会被阻塞;

  2. 常见用法:

    router.beforeEach((to, from, next) => {
      if (to.meta.requiresAuth && !isLoggedIn()) {
        next('/login'); // 重定向到登录页
      } else {
        next(); // 放行导航
      }
    });
    
  3. next() 的参数:

    参数 行为
    next() 正常放行,进入下一个守卫或目标路由
    next(false) 中断当前导航,返回 from 路由
    next(‘/path’)next({ path: ‘/login’ }) 重定向到新路由
    next(error) 导航终止,并触发 router.onError 回调
  4. 特点:

    1. ✅ 兼容性好 (Vue Router 3 & 4 都支持)
    2. ❌ 必须显式调用 next(),否则导航会卡住;

return(Vue Router 4+ 支持,推荐)

  1. Vue Router 4 开始支持直接返回一个值来控制导航行为,更符合现代 JavaScript 的编码风格 (类似 async/await)

  2. 常见用法:

    router.beforeEach((to, from) => {
      if (to.meta.requiresAuth && !isLoggedIn()) {
        return '/login'; // 重定向到登录页
      };
      // 不返回任何值(或 return undefined)相当于 next()
    });
    
  3. 返回值的类型:

    返回值 行为
    undefinedtrue 放行导航 (相当于 next())
    false 中断导航 (相当于 next(false))
    ‘/path’{ path: ‘/login’ } 重定向 (相当于 next(‘/login’))
    Error 实例 导航终止,并触发 router.onError (相当于 next(error))
  4. 特点

    1. ✅ 更简洁,避免忘记调用 next() 的问题;
    2. ✅ 支持 async/await,适合异步操作 (如 API 校验)
    3. ❌ 仅 Vue Router 4+ 支持 (Vue 3 默认使用 Vue Router 4)

完整的导航解析流程

  1. 导航被触发

  2. 调用失活组件的 beforeRouteLeave

  3. 调用全局的 beforeEach

  4. 在重用的组件里调用 beforeRouteUpdate

  5. 调用路由配置里的 beforeEnter

  6. 解析异步路由组件

  7. 在被激活的组件里调用 beforeRouteEnter

  8. 调用全局的 beforeResolve

  9. 导航被确认

  10. 调用全局的 afterEach

  11. 触发 DOM 更新

  12. 调用 beforeRouteEnter 中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入

路由元信息

  1. 路由元信息是 Vue Router 提供的一个非常有用的功能,它允许在路由配置中添加自定义数据,这些数据可以在导航守卫、组件内访问,用于实现权限控制、页面标题设置、布局选择等功能;

  2. 基本用法:

    • 在路由配置中使用 meta 字段定义元信息:
    const routes = [
      {
        path: '/dashboard',
        component: Dashboard,
        meta: {
          requiresAuth: true,  // 需要登录
          title: '控制面板'     // 页面标题
        }
      },
      {
        path: '/public',
        component: PublicPage,
        meta: {
          guestOnly: true      // 仅游客可访问
        }
      }
    ];
    
    • 在导航守卫中访问
    router.beforeEach((to, from, next) => {
      // 检查是否需要认证
      if (to.matched.some(record => record.meta.requiresAuth)) {
        if (!isAuthenticated()) {
          return '/login';
        } 
      } 
    })
    
    • 在组件内访问
    const route = useRoute();
    
    // 获取当前路由的元信息
    console.log(route.meta);
    
    // 获取匹配的所有路由记录的元信息
    console.log(route.matched.map(record => record.meta))
    

路由懒加载

  1. 路由懒加载的核心思想是:只有当访问某个路由时,才加载对应的组件代码,而不是在应用初始化时就加载所有路由组件 (首屏路由可以不使用懒加载)

  2. 使用动态 import

    // 下面代码,指定了相同的 webpackChunkName,会合并打包成一个 js 文件,把组件按组分块
    {
      path: '/about',
      component: () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/about')
    }, 
    {
      path: '/index',
      component: () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/index')
    }, 
    {
      path: '/home',
      component: () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/home')
    }
    

路由的几种模式

hash 模式

  1. 原理:

    • 这种模式是前端路由,使用 URLhash 部分来模拟不同的路径;
    • 这种模式下的 URL 类似:http://example.com/#/path
    • 因为 hash 部分不会被发送到服务器,所以服务端不需要特别处理;
  2. 实现:

    • Vue-Router 通过监听 window.onhashchange 事件来监测 URL 的变化;
    • hash 值变化时,Vue-Router 会解析 hash 部分并更新视图;
  3. 优点:

    • 简单易用,不需要服务器配置;
    • 浏览器支持良好;
  4. 缺点:

    • URL 不美观,带有 # 符号;
    • SEO 不友好,因为 hash 不会被搜索引擎索引;

history 模式

  1. 原理:

    • 利用 HTML5History API *(pushState、replaceState、go、back、forward)*来管理历史记录;
    • 这种模式下的 URL 类似:http://example.com/path,没有 #
    • 这种模式需要服务器支持,否则刷新会 404,因为浏览器在请求 URL 时会直接向服务器发送请求;
  2. 实现:

    • Vue-Router 通过监听 window.onpopstate 事件来监测 URL 的变化;
    • 使用 router.pushrouter.replace 方法会调用 history.pushStatehistory.replaceState 方法改变 URL
  3. 优点:

    • URL 美观,解构清晰;
    • 更加符合现代单页应用的路由需求;
  4. 缺点:

    • 需要服务器配置,确保所有路径都指向同一个 HTML 文件,以便客户端路由处理;
  5. 服务器配置示例 (例如 Nginx)

    location / {
      try_files $uri $uri/ /index.html;
    }
    
  6. 案例:

    1. 服务端代码
      const path = require('path')
      // 导入处理 history 模式的模块
      const history = require('connect-history-api-fallback')
      // 导入 express
      const express = require('express')
      const app = express()
      
      // 注册处理 history 模式的中间件(后端支持,不使用前端刷新浏览器会显示 404)
      app.use(history())
      // 处理静态资源的中间件(静态资源服务器),网站根目录 ../web(前端打包后的代码放在此处)
      app.use(express.static(path.join(__dirname, '../web')))
      
      // 开启服务器,端口是 3000
      app.listen(3000, () => { console.log('服务器开启,端口:3000') })
      
    2. 代码附件下载

abstract 模式

  1. 原理:

    • 这种模式主要用于非浏览器环境,比如 Node.js 服务器端渲染时;
    • 不依赖于浏览器的 History APIhash 变化;
  2. 实现:

    • Vue-Router 使用内存中存储的路由状态来模拟路由行为;
    • 没有实际的 URL 变化,完全在代码中管理路由状态;
  3. 优点:

    • 适用于没有浏览器环境的场景,比如服务器端渲染或自动化测试;
  4. 缺点:

    • 只能用于特定场景,不适合普通的前端开发;
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 路由导航守卫
    1. 1.1. 全局守卫(作用于所有路由,并支持注册多个)
      1. 1.1.1. router.beforeEach
      2. 1.1.2. router.beforeResolve
      3. 1.1.3. router.afterEach
    2. 1.2. 路由独享守卫(只作用于特定路由)
      1. 1.2.1. beforeEnter
    3. 1.3. 组件内守卫(在路由组件内定义)
      1. 1.3.1. beforeRouteEnter
      2. 1.3.2. beforeRouteUpdate
      3. 1.3.3. beforeRouteLeave
  2. 2. return、next() 控制路由行为
    1. 2.1. next()(传统方式)
    2. 2.2. return(Vue Router 4+ 支持,推荐)
  3. 3. 完整的导航解析流程
  4. 4. 路由元信息
  5. 5. 路由懒加载
  6. 6. 路由的几种模式
    1. 6.1. hash 模式
    2. 6.2. history 模式
    3. 6.3. abstract 模式