vue-router介绍

217 阅读9分钟

前言

主要是了解 vue-router 的基础使用,在接触 vue 项目时,必然会用到路由相关内容,那么就必不可少需要了解他, 本文就介绍一下 vue-router

主要参考自 vue-router参考地址

部分参考自 路由守卫以及部分源码

案例demo

vue-router

安装

pnpm add vue-router@4

router 介绍

创建项目默认给下面的 createRouter--histroy(createWebHistory 历史记录模式后面介绍),创建某个历史记录模式的,这里是默认是 h5 模式

routes

routers 就是我们的路由了,里面常见的就四个参数,path(路由)、name(路由名称)、component(组件)、children(子路由集合),还有一个 meta(路由元) 暂不介绍,基本也只有一些场景用

  • path(路由):路由path路径,也就是我们 url 后面的 **/* 之类的信息,例如:...juejin.../editor/drafts
  • component(组件):就是我们自己写的路由组件,我们的ui,可以直接导入,也可以懒加载导入(通过import函数导入,例如:import('../views/Dashboard/Location.vue'))
  • children(子路由集合):嵌套子路由,子路由和路由写法一样,只不过多了一层,一般外面会有一些公共UI用于切换,或者单纯突出层级也会嵌套子路由
  • name(路由名称):自定义的路由名称,可以简化路由 path,避免使用过长 path,或者拼写错误问题,如果每个路由都使用 name,并且统一管理name 并使用 export 的路由名称,那么再也不用担心拼写错误问题了
  • meta(路由元):可以附加一些任意信息在路由上,可以理解为制定路由额外携带的参数,路由守卫、保活或者一些场景可能会用到该参数,例如设置路由保活参数:meta: { keepAlive: true }

下面是我写的一个简易的 demo 案例中的路由,

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    { path: '/', redirect: '/login' }, //redirect重定向 不传递 / 表示相对位置,只替换最后一个路由
    {
      path: '/login',
      name: 'Login', //命名路由,可以通过 name 直接跳转到对应的路由,router.push({ name: 'Login'})
      // 懒加载,使用的时候才会加载,也就是提升了加载速度,并不会节省带宽,工程大页面多会有效果,能够提升启动速度
      component: () => Login,
    },
    { path: '/dashboard', redirect: '/dashboard/location' },
    {
      path: '/dashboard',
      name: 'Dashboard',
      component: Dashboard,
      children: [
        {
          path: '/dashboard/location',
          name: 'Location',
          component: Location,
          // component: import('../views/Dashboard/Location.vue')
        },
        {
          path: '/dashboard/menu/:id', //动态路由,这里里面的都是用的一个(实际看自己需要)
          name: 'Menu',
          component: Menu,
          children: [
            // { path: '', component: MenuHome }, //如果就希望空节点存在页面,可以path设置为空串
            {
              path: '/dashboard/menu/subMenu/:id', //动态路由,这里面的都是用的一个(实际看自己需要) :id+表示后面可以匹配1~多个 *表示后面可以匹配0~多个 ?0~1个,就不多说了
              name: 'SubMenu',
              component: Menu,
            },
          ],
        },
      ],
    },
    { path: '/:pathMatch(.*)', component: NotFound404 }, //直接匹配所有路径,但是优先级比较低,适用于匹配不到的情况
  ],
})

其中 为了演绎嵌套子路由,配合了 Element-Plus 的 menu,Dashboard 长这样哈

image.png

在 app 中注册 router 才能使用

import router from './router'
app.use(router)

动态路由匹配

动态路由匹配,也就是我们路由里面可以匹配其他自定义参数内容,可以使用 :参数名 的方式设置匹配一个参数

//这里面的都是用的一个(实际看自己需要) 
path: '/dashboard/menu/:id', 后面固定匹配一个id

那么 /dashboard/menu/1234 路由中, 1234 就是params 参数

也可以通过正则表达式过滤参数类型,过滤个数等,需要注意的是,匹配多个的情况,末尾不能再有路由

path: '/dashboard/menu/:id(\\d+)', //仅匹配纯数字
path: '/dashboard/menu/:id+', 匹配1~多个
path: '/dashboard/menu/:id*', 匹配0~多个
path: '/dashboard/menu/:id?',  //匹配一个或者0

匹配所有路由

除了标准的匹配,有时候还需要做兜底匹配,路由不正确时,跳转统一到 NotFound 即可

{ path: '/:pathMatch(.*)', component: NotFound404 }, //直接匹配所有路径,但是优先级比较低,适用于匹配不到的情况

ps:有些可能会需要跳转到无授权页面,这个就不需要兜底了,可以使用后面的守卫做

响应路由参数变化(重复使用一个路由组件)

使用带有参数的路由时需要注意的是,当用户从 /users/johnny 导航到 /users/jolyne 时,相同的组件实例将被重复使用,两个路由都渲染同个组件,也意味着组件的生命周期钩子不会被调用

const route = useRoute()

watch(() => route.params.id, (newId, oldId) => {
  // 对路由变化做出响应...
})

ps:使用场景可以参考一下掘金的内容页跳转推荐的内容页

router跳转和传参

可以使用 RouterLink 来直接跳转,不想用 RouterLink,也可以使用自己的按钮,直接 UI 模版中的 @click 使用全局 $router 对象直接跳转

<RouterLink to="/dashboard">测试RouterLink登录</RouterLink>

<div class="cursor-pointer text-center" @click="$router.push('/dashboard')">
测试#router登录
</div>

功能略复杂,可能带判断或者带参数的,可以使用 router 对象跳转,一般使用 Vue3 的 script 标签设置 setup,没有 this 那就只能使用 useRouter,如果是怀旧版,可以直接 this.$router

//template
<el-button type="primary" @click="goHome">正常登录走这是</el-button>

//script
<script setup lang="ts">
import { RouterLink, useRouter } from 'vue-router'

const router = useRouter()

const goHome = () => {
   //直接 push
  // router.push('/dashboard')
  
  //通过name跳转
  // router.push({
  //   name: 'Location'
  // })
  
  //跳转页面替换当前页面实现切换路由
  router.replace('/dashboard')
  
  //通过 query 参数实现路由传参,不需要动态路由匹配
  // router.replace({
  //   path: '/dashboard',
  //   query: {
  //     username: 'shuai',
  //   },
  // })
  
  //通过路由 params 模式传参,这里假设在 user 路由后面传递参数,需要使用动态路由匹配了
  //router.push('/dashboard/user/18181883')
}
</script>

获取 params、query参数

可以直接使用 route 来获取 router 中携带的 params、query 参数

//模版中直接使用 $route 获取参数
$route.params.id
$route.query.id

//使用this获取
this.$route.params.id
this.$route.query.id

setup 使用 useRoute() 即可

const route = useRoute()

route.params.id
route.query.id

使用props接收params参数

// 路由配置
const routes = [
  { 
    path: '/user/:id', 
    component: User, 
    props: true // 将路由参数作为组件 props 传入
  }
]

//使用 props 接收
defineProps({
  id: String
})
<template>
  <div>用户ID: {{ id }}</div>
</template>

RouterView插槽

RouterView 是 router 路由到视图的入口(插槽),通过 RouterView,我们可以将指定路由对应的组件,直接替换应用到到 RouterView所在位置,所以其是一个动态控制内容显示的一个入口

//加入页面只有一个 <RouterView /> 那么进入到该页面的时候整个页面都显示该路由对应的页面了
<RouterView />

如果被其他视图包裹起来,那么该路由组件显示的就是 RouterView 对应位置,如下所示

<div>
    <div /> //左侧
    <RouterView />
</div>

还记开始做的 dashboard 默认显示的案例图么, 左边 element-menu 右边 RouterView

<div class="flex">
    <el-menu
      ...
    ></el-menu>

    <RouterView />
</div>

image.png

因此,内部仍然可以使用 <RouterView /> 作为二级路由、三级路由组件的入口

重定向 redirect

有时候有些页面没内容,但希望让其显示我们想显示的内容,例如: 根路由没有设置的话,我们希望他跳转到 Login 路由,可以如下设置

//redirect重定向 不传递 / 表示相对位置,只替换最后一个路由
{ path: '/', redirect: '/login' },

导航守卫

可以参考导航守卫

beforeEach(全局前置守卫)

全局前置守卫 beforeEach 是在,路由跳转前触发,可以重定向、取消、继续跳转,我们可以在这里处理我们的验权等逻辑

router.beforeEach(async (to, from, next) => {
  if (未登录) {
    return { path: 'Login' }
  } else if (黑名单) {
    //直接取消跳转
    return false
  } else if (授权没通过) {
    return { path: 'NotPermission401' }
  }
  //继续跳转
  next()
})

beforeResolve(全局解析守卫)

你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,因为它在每次导航时都会触发,不同的是,解析守卫刚好会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用

router.beforeResolve(async to => {
  if (to.meta.requiresCamera) {
    try {
      await askForCameraPermission()
    } catch (error) {
      if (error instanceof NotAllowedError) {
        // ... 处理错误,然后取消导航
        return false
      } else {
        // 意料之外的错误,取消导航并把错误传给全局处理器
        throw error
      }
    }
  }
})

router.beforeResolve 是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置

afterEach(全局后置守卫)

afterEachbeforeEach 相反,他是在路由跳转完成后触发,参数包括to, from 由于此时路由已经完成跳转 所以不会再有next,因此只能做跳转完成后的操作,例如:用户行为统计等

afterEach(路由独享守卫)

beforeEnter 守卫 只在进入路由时触发,不会在 paramsquery 或 hash 改变时触发。例如,从 /users/2 进入到 /users/3 或者从 /users/2#info 进入到 /users/2#projects。它们只有在 从一个不同的 路由导航时,才会被触发。

你也可以将一个函数数组传递给 beforeEnter,这在为不同的路由重用守卫时很有用

例如:父路由和子路由都可以使用 beforeEnter。如果放在父级路由上,路由在具有相同父级的子路由之间移动时,它不会被触发

routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from, next) => {
        next()
    },
  },
]

组件路由守卫

有这三个 beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave,分别是进入、更新时、离开时

<script>
export default {
  beforeRouteEnter(to, from) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this` !
    // 因为当守卫执行时,组件实例还没被创建!
  },
  beforeRouteUpdate(to, from) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
    // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
  },
}
</script>

beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。

不过,你可以通过传一个回调给 next 来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数:

js

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通过 `vm` 访问组件实例
  })
}

注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持 传递回调,因为没有必要了:

js

beforeRouteUpdate (to, from) {
  // just use `this`
  this.name = to.params.name
}

这个 离开守卫 通常用来预防用户在还未保存修改前突然离开。该导航可以通过返回 false 来取消。

js

beforeRouteLeave (to, from) {
  const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  if (!answer) return false
}