VueRouter基础内容和导航守卫

257 阅读6分钟

Vue-Router 路由介绍

前端路由: 它是组件和路径的映射关系

在脚手架搭建的项目中或者引入vue-router并注册之后,vue提供了两个全局组件 RouterView 和 RouterLink, 提供了两个全局对象 $$router和 $route

声明式导航

<router-link to='/my'>本质是a标签</router-link>

注意:router-link 必须传一个 to 属性,属性值必须跟路由规则数组中 component 对应的path 属性值一致。

优点

自带激活时的类名: linkActiveClass 和 linkExactActiveClass

声明式导航传参

@1 查询字符串传参

(参数在url地址上),通过 query 接收值

@@1 字符串拼接传参 只要通过问号 ? 传参的,都在 query 里面

// 传参方式
<router-link to='/my?name=值1&age=值2&...'>本质是a标签</router-link>
// 获取时 标签内
<p>{{ $route.query.name }}</p>

@@2 配置对象传参

<router-link :to = '{
    path: '/my',
    query:{
        name: "值1",
        age: "值2"
    }

}'>本质是a标签</router-link>
// 接收参数
<p>{{ $route.query.age }}</p>

@2 动态路由传参

动态路由传参,接收值得时候,看 params

const routes = [
    ...
    {
        name: 'my'
        path: '/my/:name/:age',
        component: MyComponent
    }
    ...
]

动态路由传参

to属性值为字符串时

<router-link to='/my/testnam/testage'>本质是a标签</router-link>

to属性值为配置对象时

<router-link 
    :to = '{
        name: 'my'
        params:{
            name: "值1",
            age: "值2"
        }

}'>本质是a标签</router-link>
// 接收参数
<p>{{ $route.params.age }}</p>

注意:

  • 动态路由不能通过 path + params 配合,因为 path 会忽略 parmas
  1. 这里的path 指的是,RouterLink 的 to 属性的属性值不能出现 path
  • 正确方式: name(需要给对应的路由取名,也叫命名路由)+params
  • 虽然写的也是对象,但是最终会转为字符串

编程式导航

  • 不传参
    // 字符串方式
    this.$router.push('/my')
    // 对象方式
    this.$router.push({
        path: '/my'
    })
  • 传参

@1 查询字符串传参

// 字符串方式
    this.$router.push('/my?name=testname&age=24')
    // 对象方式
    this.$router.push({
        path: '/my',
        query: {
            name: 'testname',
            age: 24
        }
    })

@2 动态路由传参

// 字符串形式传参
    this.$router.push('/my/testname/age')
    // 对象方式传参
    this.$router.push({
        name: 'my',
        params:{
            name: 'testName',
            age: 24
        }
    })

路由嵌套

二级路由 path 一般不写根路径 ( / )

// 二级路由 path 一般不写根路径 ( / )
const routes = [
{
    path: '/my',
    component: 'MyComponent',
    redirect: '/my/recommend',
    // children 配置路由嵌套
    // 重定向: 首次加载的显示 redirect 指向的组件
    children: [
        // 注意: 从二级路由开始,path 不写  / ,
        // 但是跳转的时候,二级路由需要用path+ / +自己的path形成一个完整的路径
        // 重定向和此方法(完整路径方法)二选一
        // 使用完整路径方法,就不需要解path 为 recommend 的配置对象
        // {
        //     path: '',
        //     component: Recommend
        // }
        {
            path: 'recommend',
            component: Recommend
        },
        {
            path: 'pageFirst',
            componen: 'PageFirst'
        },
        {
            path: 'pageSecond',
            componen: 'PageSecond'
        }
    ]
}
]

参考:juejin.cn/post/728970…

Vue-Router 导航守卫

导航守卫主要分为三种:

  • 全局守卫 (全局导航守卫)
  • 独享路由守卫(独享导航守卫)
  • 组件内路由守卫

全局导航守卫

全局导航守卫有三种:

  • beforeEach(全局前置守卫)
  • beforeResolve(全局解析守卫)
  • afterEach(全局后置守卫)

方法使用:

import router from './router'
// router.beforeEach 全局前置守卫 进入路由之前调用
router.beforeEach((to, from, next) => { 
  next();
});
// router.beforeResolve 全局解析守卫 在beforeRouteEnter(组件内路由守卫)调用之后调用
router.beforeResolve((to, from, next) => {
  next();
});
// router.afterEach 全局后置守卫 进入路由之后
router.afterEach((to, from) => {
  console.log('afterEach 全局后置钩子');
});

路由导航参数

to: 即将要进入的目标路由对象

from: 当前导航正要离开的路由

next: 该方法用于控制导航的行为,该参数是个函数,且必须调用,否则不能进入路由(页面空白)。 它有下几个用法:

  • next():调用next()表示继续导航,即允许用户访问目标路由。
  • next('/path'):调用next('path地址')表示重定向导航到指定的路径。这可以用于在导航守卫中进行重定向,例如在未登录时将用户重定向到登录页面。
  • next({ path: '/path' }):调用next({ path: '/path' })表示重定向导航到指定的路径。与上一个用法相似,但使用了一个包含路径的对象作为参数。
  • next({ name: 'pathname'})
  • next(false):调用next(false)表示终止导航,即不允许用户访问目标路由。这通常用于进行权限验证或者登录状态检查,如果条件不满足,则终止导航。
  • next(error):调用next(error)表示终止导航并传递一个错误对象。这可以用于在导航守卫中处理错误情况,例如在网络请求失败时中止导航并显示错误信息。
  • next(callback):调用next(callback)表示异步导航,其中callback是一个回调函数。这可以用于在导航守卫中执行异步操作,例如在获取数据后再继续导航。

tips:next方法只能在导航守卫函数中调用,并且每个导航守卫函数只调用一次next方法。

全局前置守卫

router.beforeEach()

常用场景:router.beforeEach()在路由切换之前调用,可以用来进行权限验证、登录状态检查等操作。用户如果没有登录,只能访问登录界面,不能访问其他页面。用户如果登录了,就可以访问其他页面,不能访问登录页面。

router.beforeEach((to, from, next) => {
  const hasToken = getToken()
  if (hasToken) {
    if (to.path === '/login') {
      next({
        path: store.state.permission.rootRoute
      })
    } else {
      let isFirst = store.state.permission.isGetRoutes
      store.dispatch('permission/initPage').then(() => {
        if (!isFirst && to.path == '/home') {
          next({
            path: store.state.permission.rootRoute
          })
        } else {
          next()
        }
      }).catch(() => {
        next()
      })
      next()
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      next(`/login?redirect=${to.path}`)
    }
  }
})

全局解析守卫

router.beforeResolve()

常用场景:beforeResolve在路由切换之前调用,但在beforeEach 和组件内beforeRouteEnter守卫之后调用,afterEach之前调用。在该守卫中,可以在导航被确认之前执行异步操作或者获取组件数据,可以在所有组件内守卫和异步路由组件被解析之后调用。

router.beforeResolve((to, from, next) => {
  // 在这里执行一些异步操作,例如加载数据
  getFetchData().then(() => {
    next();
  });
});

全局后置守卫

router.afterEach

afterEach在路由切换之后调用,在beforeEach和beforeResolve之后,beforeRouteEnter回调之前。 常用场景:可以用来进行页面统计、错误处理等操作,通常用于页面渲染完成后的操作。该守卫没有next参数,因此不能中止导航。

例如我们可以利用全局前置守卫和全局后置守卫制作一个进度条,我们还需要引入第三方库(NProgress)及其样式,这样项目上方会出现进度条。

import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import { isRelogin } from '@/utils/request'
NProgress.configure({ showSpinner: false })

const whiteList = ['/login', '/auth-redirect', '/bind', '/register','/casLogin']
const defaultSettings = require('@/settings')

router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
    /* has token*/
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      if (store.getters.roles.length === 0) {
        isRelogin.show = true
        // 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetInfo').then(() => {
          isRelogin.show = false
          store.dispatch('GenerateRoutes').then(accessRoutes => {
            // 根据roles权限生成可访问的路由表
            router.addRoutes(accessRoutes) // 动态添加可访问路由表
            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
          })
        }).catch(err => {
            store.dispatch('LogOut').then(() => {
              Message.error(err)
              next({ path: '/' })
            })
          })
      } else {
        next()
      }
    }
  } else {
    // 没有token
    if (whiteList.indexOf(to.path) !== -1) {
      // 在免登录白名单,直接进入
      next()
    }  else {
      // 根据是否启用cas判断需要跳转的登录页地址
      // if(defaultSettings.enableCas){
      //   next(`/casLogin`)
      // }else{
        next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
      // }
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  NProgress.done()
})

路由独享守卫

beforeEnter

路由独享守卫:主要用来判断用户是否有权限访问某个页面,如果没有权限就跳转到首页。路由独享守卫仅对特定的路由生效,可以用于处理特定路由的逻辑。beforeEnter守卫只在进入路由时触发,不会在params、query或hash改变时触发 从/pagedetail/2进入到/pagedetail/3或者从/pagedetail/2#first进入到/pagedetail/2#seconds时,守卫不会触发,只有在从一个不同的路由导航时,才会被触发。

const routes = [
  {
    path: '/pageHome',
    component: PageHome,
    beforeEnter: (to, from, next) => {
      // 在进入路由之前执行一些操作
      if (isLoggedIn()) {
        // 用户已登录,允许访问路由
        next();
      } else {
        // 用户未登录,重定向到登录页面
        next('/login');
      }
    }
  },
  // ...
  // 其他路由配置...
]

组件内路由守卫

组件内路由守卫有三种:

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave

beforeRouteEnter

常用场景:在数据预加载时,在进入组件之前,需要先加载一些数据。就可以使用 beforeRouteEnter, 组件内守卫来调用接口获取数据,并在数据加载完成后再进入组件。

因为钩子在组件实例还没被创建的时候调用,所以不能获取组件实例 this,可以通过传一个回调给 next 来访问组件实例 。 但是回调的执行时机在 mounted 后面,所以在我看来这里对 this 的访问意义不太大,可以放在 created 或者 mounted 里面。

beforeRouteEnter (to, from, next) {
    console.log('在路由独享守卫后调用');
    next(vm => {
      // 通过 `vm` 访问组件实例`this` 执行回调的时机在mounted后面,
    })
  },

执行顺序: beforeEach 和独享守卫  beforeEnter 之后,全局 beforeResolve 和全局 afterEach 之前调用.

beforeRouteUpdate

beforeRouteUpdate:用于在组件复用时,路由参数发生变化时执行操作,可以通过this访问组件实例,因为组件已经挂载好了

常用场景:路由参数更新----当同一个组件在不同参数下进行切换时,可能需要根据新的参数更新组件的数据或状态。可以使用 beforeRouteUpdate 组件内守卫来处理这种情况。

beforeRouteUpdate 是在当前路由改变,但是该组件被复用时调用

例如对于一个带有动态参数的路径 /users/:id ,在 /users/1 和 /users/2 之间跳转的时候,

由于会渲染同样的 UserDetails 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 this

beforeRouteUpdate(to, from, next) {
    if (to.params.id !== from.params.id) {
      // 当路由参数 id 发生变化时,重新请求数据
      this.fetchData(to.params.id);
    }
    next();
  },

beforeRouteLeave

常用场景:

@1 数据清理:在离开当前路由之前需要执行一些清理操作,例如取消订阅事件、重置组件状态等。可以使用 beforeRouteLeave 组件内守卫来处理这些操作。

beforeRouteLeave(to, from, next) {
    if (this.hasUnsavedChanges()) {
      if (confirm('是否保存修改的数据?')) {
        this.saveData(); // 保存修改的数据
      }
    }
    next(); 
  },

@2 页面切换动画:在切换页面时添加过渡动画效果,以提升用户体验。可以在 beforeRouteEnter 和 beforeRouteLeave 组件内守卫中设置过渡动画的相关逻辑。

   beforeRouteEnter(to, from, next) {
    // 在进入组件之前设置初始过渡状态
    this.transitionName = 'slide-in';
    next();
  },
  beforeRouteLeave(to, from, next) {
    // 在离开组件之前设置过渡状态
    this.transitionName = 'slide-out';
    next();
  },

路由守卫执行的完成过程

  1. beforeRouteLeave: 路由组件的组件离开路由前钩子,可以取消路由离开。
  2. beforeEach: 路由全局前置守卫,用于登录校验。
  3. beforeRouteUpdate: 在复用组件内部路由守卫钩子
  4. beforeEnter: 路由独享守卫
  5. beforeRouteEnter: 路由组件的组件进入路由前钩子
  6. beforeResolve: 全局解析守卫
  7. afterEach: 路由全局后置钩子
  8. beforeCreate: 不能访问this
  9. created: 可以访问this,不能访问dom
  10. beforeMoute
  11. deactived: 离开缓存组件A,或者触发A的beforeDestory和destroy组件销毁钩子
  12. mouted
  13. activated: 进入缓存组件,进入a的嵌套子组件
  14. 执行beforeRouteEnter回调函数 next