点亮你的 Vue 技术栈(五):带你玩转 Vue Router 路由管理

691 阅读8分钟

「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战」。

概念

单页面应用就是用 Vue.js + Vue Router 创建的,我们只需要将组件 Component 映射到路由 Routes,然后告诉 Vue Router 在哪里渲染它们即可。Vue Router 能够轻松地管理 SPA 项目中组件的切换。

简单来说就是:Hash 地址组件之间的对应关系。

注,vue-router 目前只有 3.x4.x 的版本,其中:

本文讲解的 Vue Router 基于 3.x 版本。

快速起步

安装

npm install vue-router --save

使用

router/index.js

import Vue from 'vue'
// 导入路由模块
import VueRouter from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'

// 使用路由插件
Vue.use(VueRouter)

// 创建路由对象
const router = new VueRouter({
  // Hash 路由模式
  mode: 'hash',
  // (路由-组件)映射表
  routes: [
    {
      name: 'Home',
      path: '/home',
      component: Home
    },
    {
      name: 'About',
      path: '/about',
      component: About
    }
  ]
})

// 导出路由实例
export default router

main.js

import Vue from 'vue'
import App from './App.vue'
// 相当于导入 ./router/index.js 文件
import router from './router'

Vue.config.productionTip = false

new Vue({
  // 将路由实例挂载到 Vue 实例上
  router,
  render: h => h(App)
}).$mount('#app')

App.vue

<!-- 路由占位符 -->
<router-view></router-view>
<!-- 路由链接 -->
<router-link to="/home">首页</router-link>|
<router-link to="/about">关于</router-link>

演示效果:

router 构建选项

routes 映射表

interface RouteConfig = {
  path: string,
  component?: Component,
  name?: string, // 命名路由
  components?: { [name: string]: Component }, // 命名视图组件
  redirect?: string | Location | Function,
  props?: boolean | Object | Function,
  alias?: string | Array<string>,
  children?: Array<RouteConfig>, // 嵌套路由
  beforeEnter?: (to: Route, from: Route, next: Function) => void,
  meta?: any,

  // 2.6.0+
  caseSensitive?: boolean, // 匹配规则是否大小写敏感?(默认值:false)
  pathToRegexpOptions?: Object // 编译正则的选项
}

mode 路由模式

const router = new VueRouter({
  // 默认hash模式, 可选: hash、history、abstract
  mode: 'hash'
  routes: []
})

区别:

base 基路径

应用的基路径。例如,如果整个单页应用服务在 /app/ 下,然后 base 就应该设为 "/app/"。默认值为 /

linkActiveClass 激活类名

全局配置 <router-link> 默认的激活的 class。被激活的路由链接,默认会应用一个叫做 router-link-active 的类名,开发者可以使用此类名选择器,为激活的路由链接设置高亮的样式:

.router-link-active {
  color: tomato;
  font-weight: bold;
}

🎈自定义路由高亮的 class 类:

在创建路由的实例对象时,开发者可以基于 linkActiveClass 属性,自定义路由链接被激活时所应用的类名:

// 创建路由对象
const router = new VueRouter({
  // 自定义对高亮的 class 类, 默认的 router-link-active 会被覆盖掉
  linkActiveClass: 'router-active',
  routes: []
})

...

这里仅列出常用的选项,更多请查询官网。

命名路由

编程式导航的 $router.push() 函数可以根据命名路由name 属性进行路由跳转。

注意:命名路由的 name 值不能重复,必须保证其唯一性

{
  path: '/movie',
  // 使用 name 属性为当前的路由规则定义一个"名称"
  name: 'movie',
  component: Movie
}

路由重定向与别名

可见启动项目后的默认地址为 http://localhost:8080/,页面一片空白,若要页面一开始就呈现 Home Page 页面,则需要使用路由重定向。

👛重定向:

const router = new VueRouter({
  routes: [
    // 路由重定向: 将 '/' 重定向到 '/home'
    { path: '/', redirect: '/home' },
    { path: '/home', component: Home },
    { path: '/about', component: About }
  ]
})

👝起别名:

区别于重定向的 URL 变化。

举个例子:/home 的别名是 /main,意味着,当用户访问 /main 时,URL 会保持为 /main,但是路由匹配则为 /home,就像用户访问 /home 一样

const router = new VueRouter({
  routes: [
    // 访问 '/' 时 URL 变为 '/home', 并展示对应组件.
    { path: '/', redirect: '/home' },
    // 访问 /main 等同于访问 /home, 但 URL 保持 /main 不变!
    { path: '/home', component: Home, alias: '/main' },
    { path: '/about', component: About }
  ]
})

路由懒加载

路由懒加载的好处:当访问到某个页面才去加载相关资源,提高页面的访问速度。

{
  name: 'About',
  path: '/about',
  // 路由懒加载
  component: () => import('@/views/About.vue')
}

路由链接 & 路由占位符

router-view

router-view: 路由占位符,就是我们要跳转的页面/组件会替换这个占位符。

注:<router-view> 也可以配合 <keep-alive> 一起使用!

<router-view></router-view>

router-link

router-link: 路由链接,用于跳转到指定路径,相当于 a 标签。

<!-- to: 跳转路径 -->
<!-- tag: 渲染为指定标签 -->
<!-- replace: 跳转后不会留下历史记录 -->
<router-link to="/about" tag="button" replace>关于</router-link>

嵌套路由 | 子路由

通过路由实现组件间的嵌套展示,叫做嵌套路由。在父路由规则中,通过 children 属性嵌套声明子路由规则。

const router = new VueRouter({
  mode: 'history',
  linkActiveClass: 'router-active',
  routes: [
    { path: '/', redirect: '/home' },
    { name: 'Home', path: '/home', component: Home },
    {
      name: 'About',
      path: '/about',
      component: About,
      // 声明子路由规则
      children: [
        {
          name: 'VueJs',
          // 切勿写成 '/vuejs'(绝对路径), 'vuejs' 表示相对父路由的路径: /about/vuejs
          path: 'vuejs',
          component: VueJs
        },
        {
          name: 'vue-cli',
          path: 'vue-cli',
          component: VueCli
        },
        {
          name: 'VueRouter',
          path: 'vue-router',
          component: Router
        },
        {
          name: 'Vuex',
          path: 'vuex',
          component: Vuex
        },
      ]
    }
  ]
})

声明子路由规则后,还需要在父组件中添加子路由占位符 <router-view> 才能使得路由跳转 。

<h2>This is About Page</h2>
<router-link to="/about/vuejs">关于 vue.js</router-link>|
<router-link to="/about/vue-cli">关于 vue-cli</router-link>|
<router-link to="/about/vue-router">关于 vue-router</router-link>|
<router-link to="/about/vuex">关于 vuex</router-link>
<!-- 子路由占位符 -->
<router-view></router-view>

感受下嵌套跳转:

路由参数

路由有 2 种传参方式,分别是

  • params 传参:形如 juejin.cn/post/5945
  • query 传参:形如 juejin.cn/post?id=5945

params 方式

想要获取 RESTful 风格的路由参数,得把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性。在 vue-router 中使用 : 定义路由的参数项。比如:

<router-link to="/movie/1">浩克</router-link> |
<router-link to="/movie/2">钢铁侠</router-link> |
<router-link to="/movie/3">蜘蛛侠</router-link>

🚀这类路由称为动态路由

var router = new VueRouter({
  mode: 'hash',
  routes: [
    {
      name: 'Movie',
      // 动态路由匹配: 只匹配形如 "/movie/1" 路径、无法匹配 "/movie/1/"、"/movie/" 这类路径
      path: '/movie/:mid',
      // 指向同一个组件
      component: Movie,
    }
  ]
})

通过动态路由匹配的方式渲染出的组件,可以使用 $route.params 对象访问动态匹配的参数值。

this.$route.params.mid

效果:


为了简化路由参数的获取形式,vue-router 允许在路由规则中开启 props 接收动态路由参数。

// => router/index.js

var router = new VueRouter({
  mode: 'hash',
  routes: [
    {
      path: '/movie/:mid',
      component: Movie,
      // 开启props传参: 这个'/:id'中的 id 会被映射到 Movie 组件中 props 的 id 参数
      props: true
    }
  ]
)}

在 Movie.vue 组件中接收参数

export default {
  // index.js 路由文件的 Movie 组件中 props: true
  props: ['mid'],
  mounted() {
    console.log('mid: ' + this.mid)
  },
  updated() {
    console.log('mid: ' + this.mid)
  },
}

演示:

query 方式

通过 ? 拼接参数:

<router-link to="/post?id=14025">Passage1</router-link>

获取参数:

created() {
  console.log(this.$route.query);
  console.log(this.$route.query.id);
},

query 传参:

$router$route

🚀官网传送门


前者 $router路由组件的导航对象,后者 $route路由组件的状态对象

接下来通过一个实例了解下 $route 对象的部分属性:

$router 在下文编程式导航中使用并演示。

<!-- App.vue -->
<router-link to="/movie/1?color=red&amp;age=23">浩克</router-link> |
<router-link to="/movie/2?name=iron&amp;age=21&amp;color=red">钢铁侠</router-link> |
<router-link to="/movie/3?age=16">蜘蛛侠</router-link><br><br>

<!-- Movie.vue -->
<h2>电影ID—— {{ this.$route.params.mid }}</h2>
<h3>this.$route: 路由的参数对象</h3>
<h3>this.$router: 路由的导航对象</h3>
<h3>this.$route.path: {{ this.$route.path }}</h3>
<h3>this.$route.fullPath: {{ this.$route.fullPath }}</h3>
<h3>this.$route.params: {{ this.$route.params }}</h3>
<h3>this.$route.query: {{ this.$route.query }}</h3>

演示结果:

编程式导航

🔎 Router 实例方法全解!

为了搞懂什么是编程式导航,先来简单了解下声明式导航<router-link :to='xxx'> 导航链接就是声明式导航;而所谓编程式导航就是使用 vue-router 提供的相关 API 进行页面跳转。

声明式编程式
<router-link :to="{ name: 'movie', params: { mid: 5 } }">$router.push({ name: 'movie', params: { mid: 5 } })

⭐编程式导航相关 API 如下:

  • this.$router.push(path)
  • this.$router.replace(path)
  • this.$router.back()
  • this.$router.forward()
  • this.$router.go(n)

$router.push(path)

在 Vue 实例内部,可以通过 $router 访问路由实例。因此你可以调用 this.$router.push(),其他 API 同理。

$router.push() 的参数可以是一个字符串路径,或者一个描述地址的对象。例如:

注:通过 name 属性跳转只能是命名路由

// 字符串 ==> /movie/145?key=value
this.$router.push('/movie/145?key=value')

// 对象 ==> ./movie(相对路径)
this.$router.push({ path: 'movie' })

// 对象 ==> /movie
this.$router.push({ path: '/movie' })

// 命名路由(name属性) ==> /movie/156
this.$router.push({ name: 'movie', params: { mid: '156' }})

// 带查询参数 ==> /movie?plan=private
this.$router.push({ path: '/movie', query: { plan: 'private' }})

// path与params不能同时使用 ==> /movie
this.$router.push({ path: '/movie', params: { mid: '123' }})

💝注意:pathparams 不能同时使用,否则 params 会被忽略!

$router.replace(path)

用指定路由替换当前路由(用法同 $router.push()),但不会记载到历史记录中。

$router.back()

跳转到历史记录的上一个页面;等价于 $router.go(-1).

$router.forward()

跳转到历史记录的下一个页面;等价于 $router.go(1).

$router.go(n)

可以在浏览历史中前进和后退,n 小于 0 后退,n 大于 0 前进,n 等于 0 刷新当前页面。

导航守卫

全局导航守卫

Vue Router 有 3 个全局守卫:

  • router.beforeEach:全局前置守卫,进入路由之前
  • router.beforeResolve:全局解析守卫,在 beforeRouteEnter 调用之后调用
  • router.afterEach:全局后置钩子,进入路由之后

全局前置守卫

const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
})

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中

🚀每个守卫方法接收三个参数:

  • to: 即将要进入的目标 路由对象
  • from: 当前导航正要离开的路由。
  • next: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
    • next(): 进行管道中的下一个钩子
    • next(false): 中断当前的导航
    • next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: truename: 'home' 之类的选项以及任何用在 router-linkto 属性router.push 中的选项。
    • next(error): 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

🌈确保 next 函数在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。举个栗子:

var router = new VueRouter({
  routes: []
})

// 全局前置守卫
router.beforeEach((to, from, next) => {
  // to: 即将要访问的 $route 路由信息对象
  // from: 表示将要离开的 $route 路由信息对象
  // next: 1.next(false)  2.next()  3.next('/login')
  if (to.path != '/login') {
    var token = localStorage.getItem('token')
    if (token) {
      next()
    } else {
      next('/login')
    }
  } else {
    next()
  }
})

全局解析守卫

你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫异步路由组件被解析之后,解析守卫就被调用。

// 全局解析守卫
router.beforeResolve((to, from, next) => {
  next()
})

全局后置钩子

你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

// 全局后置钩子
router.afterEach((to, from) => {
  // ...
})

路由独享守卫

如果你不想全局配置守卫的话,你可以为某些路由单独配置守卫(路由独享守卫):

路由独享守卫仅有一种: router.beforeEnter

var router = new VueRouter({
  routes: [
    {
      name: 'movie',
      path: '/movie/:mid',
      component: Movie,
      // 路由独享守卫
      beforeEnter: (to, from, next) => {
        console.log('hello, movie!');
        next()
      },
    }
  ]
})

组件内的守卫

可以在路由组件内直接定义以下 3 种路由导航守卫:

  • beforeRouteEnter: 不能获取组件实例 this,因为当守卫执行前,组件实例还没被创建。
  • beforeRouteUpdate: 在当前路由改变,但是该组件被复用时调用(即:在动态路由间跳转时)的情况下执行,可以访问组件实例 this
  • beforeRouteLeave: 导航离开该组件的对应路由时调用,可以访问组件实例 this
export default {
  data() {
    return {}
  },
  beforeRouteEnter(to, from, next) {
    // 进入该路由时执行, 可以通过回调访问this组件
    console.log('enter')
    next()
  },
  // 动态路由切换时执行(/movie/:mid)
  beforeRouteUpdate(to, from, next) {
    // 该路由参数更新时执行:例如从 /movie/1 跳转到 /movie/2 时会执行
    console.log('update')
    next()
  },
  // 这个离开守卫通常用来禁止用户在还未保存修改前突然离开, 该导航可以通过 next(false) 来取消
  beforeRouteLeave(to, from, next) {
    // 离开该路由时执行
    console.log('leave')
    next()
  },
}

❤️/ END / 如果本文对你有帮助,点个「赞」支持下吧。