Vue Router

本文将介绍 Vue Router,Vue 官网的路由管理器。

一、什么是 Vue Router?

1. 什么是路由?

路由是通过互联的网络把信息从源地址传输到目的地址的活动

路由器提供了两种机制:

  • 路由:决定数据包从来源到目的地的路径
  • 传送:将输入端的数据转移到合适的输出端

2. 什么是前端路由?

具体请看:

SPA-阶段:网站应用化 - 前端架构 渲染模式的演进

3. 如何修改 URL 并使页面不刷新?

(1) hash

URL 的 hash ,即页面的锚点,代表网页中的一个位置。锚点是 URL 中 # 之后的部分,例如:

1
http://www.example.com/index.html#锚点

URL 中的 hash 有以下几点特性:

  • hash 不包括在 HTTP 请求中
  • 改变 hash 并不会触发页面重载
  • 改变 hash 会改变浏览器的访问记录

(2) history

HTML5 提供 history 接口,通过 state 的形式修改 URL,其实现函数是 pushState 和 replaceState。

修改 URL
1
2
3
4
5
// 用于修改URL,有历史记录,可前进可后退
window.history.pushState(state, title, url);

// 用于替换URL,且无历史记录
window.history.replaceState(state, title, url);

其中,

  • state 是对象,可以为空
  • title 是字符串,可以为空
  • url 用于设置 URL 地址
前进后退:
1
2
3
4
5
6
// 前进与后退
window.history.forword()
window.history.back()

// 前进与后退若干个页面
window.history.go(数字)

二、基本使用

1. 安装

(1) 法 1

1
npm install vue-router --save

(2) 法 2

通过 Vue CLI 创建项目,并选择 Vue Router 。

2. 配置

router.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 导入vue
import Vue from 'vue'
// 导入vue-router
import Router from 'vue-router'
// 导入组件
import HelloWorld from '@/components/HelloWorld'

// 安装插件
Vue.use(Router)

export default new Router({
routes: [
···路由映射表···
]
})

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Vue from 'vue'
import App from './App'
// 导入router
import router from './router'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
el: '#app',
// 挂载router
router,
render: h => h(App)
})

3. 路由映射表

router.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default new Router({
// 配置路径与组件之间的映射关系
routes: [
{
// 路径后缀
path: '/',
// 组件
component: HelloWorld
},
{
// 路径后缀
path: '/',
// 组件
component: HelloWorld
},
]
})

修改默认路径:

增加一个空路径的映射,并将它重定向至首页。

1
2
3
4
5
6
7
8
9
10
routes: [
{
path: '',
redirect: '/home'
},
{
path: '/home',
component: Home
}
]

4. router-view

为了在跳转路径后显示对应的组件,应该在 App.vue 的 HTML 模板中设置 router-view ,用于渲染路径所匹配的组件。

在跳转路径后,router-view 将会动态渲染为对应的组件,并且切换时仅有 router-view 会刷新,而其它内容不会改变。

1
2
3
4
5
6
7
<template>
<div id="app">
···公共模块···
<router-view/>
···公共模块···
</div>
</template>

5. 修改路由模式

可以在 Vue Router 的 hash 模式和 history 模式之间切换,通过修改 router/index.js 实现。

router.js

1
2
3
4
5
6
7
8
export default new Router({
// 修改路由模式
mode: 'history',
// 配置路径与组件之间的映射关系
routes: [
···
]
})

三、跳转路径

1
<router-link to="路径后缀">超链接名</router-link>

router-link 用于页面跳转,会被默认渲染为 a 标签

  • tag='标签名' ,用于指示超链接的样式

  • replace ,用于将链接跳转设为 replaceState 模式(即没有历史记录,不可以前进后退)

  • actice-class=active ,用于修改标签为 active 状态时的类名

    默认情况下,Vue Router 会为处于 active 状态的 router-link 标签增设一个类名,默认为 router-link-active 。

    也可以通过设置 router/index.js ,从而将修改所有 active 状态的 router-link 标签的新增类名。

2. 手动跳转路径

不要直接使用 hash 或 history 直接修改路径,而是应该让 Vue Router 帮忙做。

在 Vue 实例内部,通过 $router 访问路由实例。

1
2
3
4
5
this.$router.push('路径后缀')

this.$router.replace('路径后缀')

this.$router.go(数字)

四、嵌套路由

1. 什么是嵌套路由

在实际的应用场景中,我们可能希望通过 /home/news 访问 home 下的新闻,通过 /home/message 访问 home 下的信息

使用嵌套路由,便可以很好地实现这一效果。

2. 嵌套路由的实现

Home.vue

  • 为两个子组件设置链接
  • 增加 router-view ,以便渲染为子组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div>
<h1>HOME</h1>
<h2>这是home</h2>
<router-link to="/home/message">信息</router-link>
<router-link to="/home/news">新闻</router-link>
<router-view/>
</div>
</template>

<script>
export default {
name: 'Home'
}
</script>

router.js

  • 引入两个子组件

  • 在父路由的 children 中填入两个子组件

    path 不应该加 /

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const Homemessage = () => import('../components/Homemessage.vue')
const Homenews = () => import('../components/Homenews.vue')

Vue.use(Router)

export default new Router({
mode: 'history',
routes: [
{
path: '/home',
component: Home,
children: [
{
path: 'message',
component: Homemessage,
},
{
path: 'news',
component: Homenews,
}
]
},
]
})

五、动态路径

1. 什么是动态路径?

在某些情况下,页面的路径并不是固定的。

比如一般用户界面对应的路径:

1
/user/[userid]

2. 解决方法

router-link 应该能够指定动态生成的路径,因此正确的做法是:

  • 实现获取动态路径中的动态参数
  • 通过计算属性等方法拼接动态路径
  • 通过 v-bind 将动态路径绑定至 router-link 的属性中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div id="app">
<router-link :to="userurl">{{userid}}</router-link>
</div>
</template>

<script>
export default {
name: 'App',
data(){
return {
userid: 'zhangshan',
}
},
computed: {
userurl: function() {
return '/User/'+this.userid;
}
}
}
</script>

(2) 路由映射表

对于路径中的 “动态参数”,应该使用 :参数名 来占位。

1
2
3
4
5
routes: [
{
path: '/.../:参数名/...',
}
]

设置后,类似 /.../XXX/... 的路径都将被匹配,映射到相同的组件。

匹配到路由后,参数将会被设置到 this.$route.params 之上,可以在组件中调用这一参数。

(3) 获取动态参数

可以通过 this.$route.params.参数名 来获取当前路径下的动态参数。

假设动态路径在路由映射表中配置如下:

1
2
3
4
5
routes: [
{
path: '/users/:id',
}
]

当前路径为:

1
/users/1001

则可以通过 this.$route.params.id 获得当前的 id 动态参数,其值为 1001 。

六、路由懒加载

1. 什么是路由懒加载?

单页面富应用只有一张 HTML 页面,在用户与应用程序交互时动态更新页面。

这种方式有许多优点,但由于所有的资源都放在一起,初次加载时耗时会较长。

因此,有时需要将不同路由对应的组件分割成不同的代码块,当路由被访问时才加载对应组件,这便是路由懒加载。

2. 路由懒加载的实现

路由懒加载 | Vue Router

  • 不再直接 import 组件

  • 通过异步函数来 import 组件

    1
    const Foo = () => import('组件路径')

非懒加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Vue from 'vue'
import Router from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(Router)

const routes = [
{
path: '/home',
component: Home
}
]

const router = new Router({
routes
})

export default router

懒加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Vue from 'vue'
import Router from 'vue-router'
const Home = () => import('../views/Home.vue')

Vue.use(Router)

const routes = [
{
path: '/home',
component: Home
}
]

const router = new Router({
routes
})

export default router

七、参数传递

1. params

  • 路由映射表配置:将路由中的路径设为 /页面/:参数名

  • 传递方式:/页面/参数

  • 传递后形成的路径:路径/参数

  • 获取参数:this.$route.params.参数名

2. query

  • 路由映射表配置:路由中的路径无需额外配置

  • 传递方式:

    1
    2
    3
    4
    5
    6
    7
    <router-link :to="{
    path: '/页面',
    query: {
    name: 'xiaozhang',
    age: 18,
    }
    }"></router>
    • 通过 v-bind 绑定属性,以便于解析对象(而不是将它当作字符串)
    • 在 path 指定跳转的页面
    • 在 query 中填入需要传递的内容
  • 传递后形成的路径:

  • 获取参数:this.$route.query.参数名

八、导航守卫

1. 什么是导航守卫?

Vue Router 提供的导航守卫主要用来监听路由的进入和离开,并提供了 beforeEach 和 afterEach 等方法,可以在路由跳转时执行相应的操作。

可以用于修改页面标题、防止用户直接通过修改 URL 访问特定页面等场景。

2. 全局前置守卫

1
2
3
4
router.beforeEach((to, from, next) => {
next()
···
})

守卫方法接收三个参数:

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

  • from:即将离开的路由对象

  • next():一个方法,应该保证该方法在任何导航守卫中都被严格调用一次

    • next() 代表放行
    • next('/路径') 代表跳转至指定路径
    • next({path: '/路径', ···}) 代表跳转至指定路径,可以在对象中设置诸如 replace: truename: 'home' 等选项

    next(...) 将会中断当前的导航,并进入新的导航,因此新的导航也会被导航守卫拦截。

示例-在跳转页面时修改标题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Vue.use(Router)

const router = new Router({
mode: 'history',
routes: [
{
name: '你好',
path: '/helloworld',
component: HelloWorld
}
]
})

router.beforeEach((to, from, next) => {
next()
document.title = to.name
})

export default router

2. 全局解析守卫

在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。

这和 router.beforeEach 类似,区别是:在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

3.全局后置钩子

和守卫不同的是,它不会接受 next 函数也不会改变导航本身。

因为页面已经跳转完了

1
2
3
router.afterEach((to, from) => {
...
})

4. 路由独享守卫

可以为某一个路由单独配置守卫。

1
2
3
4
5
6
7
8
9
10
11
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})

5. 组件内的守卫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不能获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}

6. 完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

九、keep-alive

1. 什么是 keep-alive?

keep-alive 是 Vue 内置的组件,可以使被包含的组件保留状态、避免重新渲染。

如果将 router-view 放置在 keep-alive 之中,则所有路径匹配到的视图组件都将被缓存,而不会被销毁

1
2
3
<keep-alive>
<router-view/>
</keep-alive>

2. activated() 和 deactivated()

被 keep-alive 包裹的组件可以调用 activated() 和 deactivated() 这两个生命周期钩子函数

  • activated():组件进入活跃状态时执行

  • deactivated():组件离开活跃状态时执行

1
2
3
4
5
6
7
8
9
10
export default {
name: '组件名',
···
activated() {
···
},
deactivated() {
···
}
}

3. include 和 exclude

keep-alive 有两个重要属性:

  • include:字符串或正则表达式,只有匹配的组件才会被缓存
  • exclude:字符串或正则表达式,只有不匹配的组件才会被缓存
1
2
3
<keep-alive exclude="字符串或正则表达式">
<router-view>
</keep-alive>

参考