Skip to content

Vue Router

第一章:初识

一、概述

1. SPA

单页面应用程序(英文名:Single Page Application)简称 SPA。顾名思义,指的是一个 Web 网站中只有唯一的一个 HTML 页面,所有的功能与交互都在这唯一的一个页面内完成。这就需要用到路由。

一个路由(route)就是一个映射关系(key - value),多个路由需要路由器(router)进行管理。

总结:前端路由 key 是路径,value 是组件。


官方文档:Vue Router

2. 快速入门

1)安装 Vue Router

bash
# 安装最新版 vue-router
npm i vue-router

# vue2 项目,要指定版本安装
npm i vue-router@3.5.2

说明

2022 / 07 以后,安装 vue-router 时,默认版本改为了 4,而 4 版本只能在 Vue3 中使用。只有 vue-router3 才能在 Vue2 使用。

2)引入与应用插件

@/main.js

javascript
// 1. 导入所需模块
import Vue from 'vue'
import VueRouter from 'vue-router'

// 2. 调用 Vue.use() 函数,将 VueRouter 安装为 Vue 的插件
Vue.use(VueRouter)

3)编写 router 配置项

新建 @/router/index.js 文件,编写路由配置。

javascript
// 引入VueRouter
import VueRouter from 'vue-router'
// 引入Luyou组件
import About from '../components/About'
import Home from '../components/Home'

// 配置路由规则
const routes = [
  {
    path:'/about',
    component:About
  },
  {
    path:'/home',
    component:Home
  }
]

// 创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
  mode: 'history', // 或者是 hash
  routes,
})

// 暴露router
export default router

4)在 main.js 中创建 vm 时传入 router 配置项

javascript
......
// 引入router
import router from './router'
......

// 创建vm
new Vue({
  el:'#app',
  render: h => h(App),
  router
})

5)实现切换(active-class 可配置高亮样式)

vue
<router-link active-class="active" to="/about">About</router-link>

6)指定展示位置

vue
<router-view></router-view>

任何子路由都是在其父路由的组件中切换显示,不管是多少层的路由嵌套。因此,在其父路由上需要写上 <router-view /> 承载容器来存放子路由组件。

几个注意点

① 路由组件通常存放在 pages|views 文件夹,一般组件通常存放在 components 文件夹。

② 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。

③ 每个组件都有自己的 $route 属性,里面存储着自己的路由信息。

④ 整个应用只有一个 router,可以通过组件的 $router 属性获取到。

二、必知

1)replace 属性

① 作用:控制路由跳转时操作浏览器历史记录的模式。

② 浏览器的历史记录有两种写入方式:push 和 replace。push 是追加历史记录,replace 是替换当前记录。路由跳转时候默认为 push。

③ 如何开启 replace 模式?

vue
<!-- 完整写法 -->
<router-link :replace="true" .......>News</router-link>
<!-- 简写 -->
<router-link replace .......>News</router-link>
2)链接高亮 (排它)

为什么不用 a 标签,而用 router-link 标签?

  • 使用 router-link 也会解析为 a 标签。
  • 会给当前访问的超链接,自动加两个类名(通过给下述两个类添加样式,实现导航链接的高亮效果)。
    • router-link-exact-active --- 精确匹配类名
    • router-link-active --- 模糊匹配类名

如何更改默认给我们添加的类名?

javascript
const router = new VueRouter({
  routes: [...],
  linkActiveClass: "类名1",
  linkExactActiveClass: "类名2"
})

实用技巧

假如在 App.vue 文件中编写了 router-link-active 高亮样式。

css
.router-link-active {
  color: red !important;
}

页面有多个歌手列表,每一项路由路径是 /discover/artlist?id=x。当我点击了孙楠时,会给其他歌手添加一个 router-link-active 类,其他歌手也会高亮。

怎么办?在 artlist 组件里可以这样书写。

css
a {
  color: #424242 !important;
}

a.router-link-exact-active {
  color: red !important;
}

2. 缓存路由组件

1)作用:让不展示的路由组件保持挂载,不被销毁。

2)具体编码

vue
<keep-alive include="News"> 
  <router-view></router-view>
</keep-alive>

<keep-alive :include="['News','Message']"> 
  <router-view></router-view>
</keep-alive>

include 写的是组件名(组件自身的 name 属性),不写 include 属性,将会缓存所有组件。

3. 路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们可以把不同的代码组件分割成不同的代码块,然后在访问的时候才加载对应的代码块,这样会更加灵活。也可以提高首屏的渲染效率。

实际上还是我们前面讲到的 webpack 的打包知识,而 Vue Router 默认就支持动态加载组件。这是因为 component 可以传入一个组件,也可以接收一个函数,该函数要返回一个 Promise。而 import 函数就是返回一个 Promise。这样就会更加高效。

javascript
const routes = [
  { path: '/', redirect: '/home' },
  { path: '/home', component: () => import('./pages/Home.vue') },
  { path: '/about', component: () => import('./pages/About.vue') }
]

我们会发现分包是没有一个很明确的名称的,其实 webpack 从 3.x 开始支持对分包进行命名。

javascript
component: () => import(/* webpackChunkName: "home-chunk" */ '../pages/Home.vue')

4. 两个新的生命周期钩子

1)作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。

2)具体名字:

① activated 路由组件被激活时触发。

② deactivated 路由组件失活时触发。

第二章:路由规则

一、多级路由 (嵌套路由)

1. 常见用法

1)配置路由规则,使用 children 配置项

注意:看是几级路由不能看路径,要看在路由配置里是否在 children 里。

javascript
routes:[
  {
    path:'/about',
    component:About,
  },
  {
    path:'/home',
    component:Home,
    children:[ // 通过children配置子级路由
      {
        path:'news', // 此处一定不要写: /news
        component:News
      },
      {
        path:'message', // 此处一定不要写: /message
        component:Message
      }
    ]
  }
]

2)跳转(要写完整路径)

vue
<router-link to="/home/news">News</router-link>

2. 细节

上面例子中,路由为 /home 时,Home 组件中的 router-view 里面什么都不会呈现,因为没有匹配到嵌套路由。想让它呈现,怎么办?

javascript
routes: [
  // ...
  {
    path: '/home',
    component: Home,
    children: [
      {
        path: '',
        component: HomeHome
      },
      {
        path: 'news',
        component: News
      }
      // ...其他子路由
    ]
  }
]

二、命名路由

1. 常见用法

1)作用:可以简化路由的跳转。

2)如何使用

① 给路由命名

javascript
{
  path:'/demo',
  component:Demo,
  children:[
    {
      path:'test',
      component:Test,
      children:[
        {
          name:'hello' // 给路由命名
          path:'welcome',
          component:Hello,
        }
      ]
    }
  ]
}

② 简化跳转

vue
<!--简化前,需要写完整的路径 -->
<router-link to="/demo/test/welcome">跳转</router-link>

<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'hello'}">跳转</router-link>

2. 细节

在一些场景中,可能希望导航到命名路由而不导航到嵌套路由。例如,想导航 /user/:id 而不显示嵌套路由。那样的话,可以命名父路由,但请注意重新加载页面将始终显示嵌套的子路由,因为它被视为指向路径/users/:id 的导航,而不是命名路由。

javascript
const routes = [
  {
    path: '/user/:id',
    name: 'user-parent',
    component: User,
    children: [{ path: '', name: 'user', component: UserHome }],
  },
]

现象:

javascript
// 通过名称导航
router.push({ name: 'user-parent', params: { id: 123 } })
// Vue Router 思考: "用户明确指定了 name: 'user-parent'"
// → 只导航到父路由, 不管子路由


// 通过路径访问 /user/123
// Vue Router 思考: "根据路径匹配规则"
// → /user/123 匹配父路由的 path: '/user/:id'
// → 发现有 path: '' 的子路由
// → 自动匹配并显示子路由

三、其他

1. 路由重定向

javascript
{
  path: '/',
  redirect: '/discover/tuijian' // 重定向路由为路由 path
}

{
  path: '/',
  redirect: { name: '/discover/tuijian' } // 重定向路由为命名路由
}

在写 redirect 的时候,可以省略 component 配置,因为它从来没有被直接访问过,所以没有组件要渲染。唯一的例外是嵌套路由:如果一个路由记录有 children 和 redirect 属性,它也应该有 component 属性。

2. 404 处理

如果想匹配任意路径,可以使用自定义的 路径参数 正则表达式,在 路径参数 后面的括号中加入正则表达式:

javascript
const routes = [
  // 将匹配所有内容并将其放在 `route.params.pathMatch` 下
  { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
  // 将匹配以 `/user-` 开头的所有内容,并将其放在 `route.params.afterUser` 下
  { path: '/user-:afterUser(.*)', component: UserGeneric },
]
javascript
{
  path: '*',
  component: () => import('@/views/NotFound.vue')
}

第三章:路由传参

1. query 参数

① 传递参数

vue
<!-- 跳转并携带query参数, to的字符串写法 -->
<router-link to="/home/message/detail?id=666&title=你好">跳转</router-link>
<router-link :to="`/home/message/detail?id=${id}&title=${title}`">跳转</router-link>

<!-- 跳转并携带query参数, to的对象写法 -->
<router-link
  :to="{
    path: '/home/message/detail',
    query: {
      id: 666,
      title: '你好'
    }
  }"
>跳转</router-link>

② 接收参数

javascript
$route.query.id
$route.query.title

2. params 参数

① 配置路由,声明接收 params 参数

javascript
{
  path:'/home',
  component:Home,
  children:[
    {
      path:'news',
      component:News
    },
    {
      path: 'message',
      component:Message,
      children:[
        {
          name:'xiangqing',
          path:'detail/:id/:title', // 使用占位符声明接收params参数
          component:Detail
        }
      ]
    }
  ]
}

// 注: 在配置路由的时候, 在占位的后面加上一个问号, 表示params可以传递或者不传递
// 比如: path: '/search/:keyword?',

② 传递参数

vue
<!-- 跳转并携带params参数, to的字符串写法 -->
<router-link :to="/home/message/detail/666/你好">跳转</router-link>
<router-link :to="`/home/message/detail/${m.id}/${m.title}`"></router-link>

<!-- 跳转并携带params参数, to的对象写法 -->
<router-link
  :to="{
    name: 'xiangqing',
    params: {
      id: 666,
      title: '你好'
    }
  }"
>跳转</router-link>

特别注意:路由携带 params 参数时,若使用 to 的对象写法,则不能使用 path 配置项,必须使用 name 配置!

③ 接收参数

javascript
$route.params.id
$route.params.title

在 template 中,直接通过 $route.params 获取值。

在 created 中,通过 this.$route.params 获取值。

在 setup 中,需要用 vue-router 库的一个 useRoute hook。该 Hook 会返回一个 Route 对象,对象中保存着当前路由相关的的值。

3. props 配置

作用:让路由组件更方便的收到参数。

javascript
{
  name: 'xiangqing',
  path: 'detail/:id',
  component: Detail,

  // 第一种写法: props值为对象, 该对象中所有的key-value的组合最终都会通过props传给Detail组件
  // props: {a:900}

  // 第二种写法: props值为布尔值, 布尔值为true, 则把路由收到的所有params参数通过props传给Detail组件
  // props: true
   
  // 第三种写法: props值为函数, 该函数返回的对象中每一组key-value都会通过props传给Detail组件
  props(route) {
    return {
      id: route.query.id,
      title: route.query.title
    }
  }
}

面试题

  1. 路由传递参数(对象写法)path 是否可以结合 params 参数一起使用?

    答:路由跳转传参的时候,对象的写法可以是 name、path 形式,但需要注意的是,path 这种写法不能与 params 参数一起使用。

  2. 如何指定 params 参数可传可不传?

    如果路由要求传递 params 参数,但是你就不传递 params 参数,URL 就会有问题
    比如: 配置路由的时候,占位了 (params 参数),但是路由跳转的时候就不传递,路径就会出现问题:
    http://localhost:8080/#/?k=QWE
    http://localhost:8080/#/search?k=QWE (正常情况下)
    
    如何指定 params 参数可以传递、或者不传递,在配置路由的时候,在占位的后面加上一个问号
  3. params 参数可以传递也可以不传递,但是如果传递的是空串,如何解决?

    使用 undefined 解决 params 参数可以传递、不传递(空的字符串)。

    javascript
    this.$router.push({
      name: 'search',
      params: {
        keyword: '' || undefined
      },
      query: {
        k: this.keyword.toUpperCase()
      }
    });
  4. 路由组件能不能传递 props 数据?

    可以总共有三种写法。

    javascript
    // 布尔值写法: params 转 props
    props: true,
    
    // 对象写法: 额外的给路由组件传递一些 props
    props: { a: 1, b: 2 },
    
    // 函数写法: 可以params参数、query参数,通过props传递给路由组件
    props: ($route) => {
      return {
        keyword: $route.params.keyword,
        k: $route.query.k
      }
    }

二、编程式路由导航

1)作用:不借助 <router-link> 实现路由跳转,让路由跳转更加灵活。

2)API:在配置路由时,创建了一个 router 对象,接着把它放到了 Vue 的配置对象中。之后 Vue 会自动把它注入到组件实例上,属性名为 $router(这个值是全局唯一的)。

javascript
// *************************************************************** 字符串
this.$router.push('路由路径')
this.$router.replace('路由路径')

// *************************************************************** 对象
// ********************************************* push
// 写法一:对象格式,用 path 进行跳转
this.$router.push({
  path: '完整路由地址',
  query: {
    参数: '值',
    参数: '值'
  },                      // query参数会拼接成  /xxxxx?参数=值&参数=值
  // params: {
  //   参数: '值',
  //   参数: '值'
  // },                   // 有问题,不用。参数不能拼接到url后面
})

// 写法二:对象格式,用 name 进行跳转
this.$router.push({
  name: '路由的name值',
  query: {
    参数: '值',
    参数: '值'
  },                      // /xxxxx?参数=值&参数=值
  params: {
    参数: '值',
    参数: '值'
  },                      // 参数会拼到url后面    /xxxxx/3/绿皮书
})

// 带 hash,结果是 /about#team
this.$router.push({ path: '/about', hash: '#team' })

router.push({ path: '/home', replace: true })
// 相当于
router.replace({ path: '/home' })

// ********************************************* replace
this.$router.replace({
  name:'路由的name值',
  params:{
    参数: '值',
    参数: '值'
   }
})

// *************************************************************** 前进后退
this.$router.forward() // 前进
this.$router.back()    // 后退
this.$router.go(n)     // 可前进也可后退 里面传递的是数字,正数前进几步,负数后退几步
跳转类型query 参数(id=1&name=xx)params 参数(/movie/1)
path×
name
完整字符串

第四章:路由守卫

作用:对路由进行权限控制。

分类:全局守卫、独享守卫、组件内守卫

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

  • to: Route:即将要进入的目标路由对象
  • from: Route:当前导航正要离开的路由。
  • next: Function:一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
    • next():进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
    • next(false):中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
    • next('/') 或者 next({ path: '/' }):跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: truename: 'home' 之类的选项以及任何用在 router-linkto proprouter.push 中的选项。
    • next(error):(2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

在 Vue3.x 中,不在推荐使用 next() 方法了。而是使用返回值:

  • false:取消当前导航。
  • 不返回或者 undefined:进行默认导航。
  • 返回一个路由地址:
    • 可以是一个 string 类型的路径。
    • 可以是一个对象。对象中包含 path 、query 、params 等信息。
1)全局守卫

@/router/index.js

javascript
// 全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to, from, next)=>{
  console.log('beforeEach', to, from)
  if(to.meta.isAuth){ // 判断当前路由是否需要进行权限控制
    if(localStorage.getItem('school') === 'hj44d45fh6*'){ // 权限控制的具体规则
      next() // 放行
    }else{
      alert('暂无权限查看')
      next({name:'help'})
    }
  }else{
    next() // 放行
  }
})

// 全局后置守卫:初始化时执行、每次路由切换后执行
router.afterEach((to, from)=>{
  console.log('afterEach', to, from)
  if(to.meta.title){
    document.title = to.meta.title // 修改网页的title
  }else{
    document.title = 'vue_test'
  }
})
2)独享守卫

只作用于某个特定的路由,这些守卫定义在路由配置对象上。只有前置独享守卫,无后置独享守卫。

javascript
const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

可以把全局守卫的前置守卫代码放到独享守卫里面,如下:

javascript
beforeEnter(to, from, next){
  console.log('beforeEnter', to, from)
  if(to.meta.isAuth){ // 判断当前路由是否需要进行权限控制
    if(localStorage.getItem('school') === 'hj44d45fh6*'){
      next()
    }else{
      alert('暂无权限查看')
      // next({name:'guanyu'})
    }
  }else{
    next()
  }
}
3)组件内守卫

只作用于某个特定的组件内部,这些守卫可以在组件内使用。

javascript
// 进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},

// 离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}

6. 路由器的两种工作模式

1)对于一个 url 来说,什么是 hash 值?url 路径上的 # 及其后面的内容就是 hash 值。

2)hash 值不会包含在 HTTP 请求中,即 hash 值不会带给服务器。

3)hash 模式:

① 地址中永远带着 # 号,不美观。

② 若以后将地址通过第三方手机 app 分享,若 app 校验严格,则地址会被标记为不合法。

③ 兼容性较好。

4)history 模式:

① 地址干净,美观 。

② 兼容性和 hash 模式相比略差。

③ 应用部署上线时需要后端人员支持,解决刷新页面服务端 404 的问题。

注意:前端人员如果使用 Node.js 部署时,可以使用 connect-history-api-fallback 包来解决问题。

第五章:进阶

一、滚动行为

使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置。vue-router 可以自定义路由切换时页面如何滚动。

当创建一个 Router 实例时,可以提供一个 scrollBehavior 方法:

javascript
const router = createRouter({
  history: createWebHashHistory(),
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    // return 期望滚动到哪个的位置
  }
})

该函数可以返回一个 ScrollToOptions 位置对象:

javascript
const router = createRouter({
  scrollBehavior(to, from, savedPosition) {
    // 始终滚动到顶部
    return { top: 0 }
  },
})
5)router 其他配置项
javascript
{
  path: '/about',
  name: 'about-router',
  component: () => import('../pages/About.vue'),
  meta: {
    name: 'why',
    age: 18
  }
}

在组件中访问 meta 属性

vue
<template>
  <div>
    <h1>{{ name }}</h1>
    <p>{{ age }}</p>
  </div>
</template>

<script>
export default {
  computed: {
    name() {
      return this.$route.meta.name;
    },
    age() {
      return this.$route.meta.age;
    }
  }
};
</script>
preview
图片加载中
预览

Released under the MIT License.