路由的概念和原理

什么是路由

路由(英文:router)就是对应关系

SPA和路由

SPA(单页面应用程序) 指的是一个 web 网站只有唯一的一个 HTML 页面,所有组件的展示与切换都在这唯一的一个页面内完成。此时,不同组件之间的切换需要通过前端路由来实现。

在 SPA 项目中,不同功能之间的切换,要依赖于前端路由来完成!

前端路由的工作方式

  1. 用户点击了页面上的路由链接
  2. 导致了 URL 地址栏中的 Hash 值发生了变化
  3. 前端路由监听了到 Hash 地址的变化
  4. 前端路由把当前 Hash 地址对应的组件渲染都浏览器中

vue-router

什么是 vue-router

vue-router 是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目中组件的切换。

vue-router 的官方文档地址

vue-router 安装和配置

  1. 安装 vue-router 包
    1
    npm install vue-router@3.5.2 -S
  2. 创建路由模块
    在 src 源代码目录下,新建 router/index.js 路由模块,并初始化如下的代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 1. 导入 vue 和 vuerouter 的包
    import Vue from 'vue'
    import VueRouter from 'vue-router'

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

    // 3. 创建路由的实例对象
    const router = new VueRouter()

    // 4. 向外共享router实例对象
    export default router
  3. 导入并挂载路由模块
    src/main.js 入口文件中,导入并挂载路由模块
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import Vue from 'vue'
    import App from './App2.vue'
    // 导入路由模块,目的:拿到路由的实例对象
    // 在进行模块化导入的时候,如果给定的是文件夹,则默认导入这个文件夹下,名字叫做 index.js 的文件
    import router from '@/router'

    Vue.config.productionTip = false

    new Vue({
    render: h => h(App),
    // 在 Vue 项目中,要想把路由用起来,必须把路由实例对象,通过下面的方式进行挂载
    // router: 路由的实例对象
    router
    }).$mount('#app')
  4. 声明路由链接和占位符
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <template>
    <div class="app-container">
    <h1>App2 组件</h1>
    <!--路由链接--->
    <!-- 当安装和配置了 vue-router 后,就可以使用 router-link 来替代普通的 a 链接了 -->
    <router-link to="/home">首页</router-link>
    <hr />
    <!-- 只要在项目中安装和配置了 vue-router,就可以使用 router-view 这个组件了 -->
    <!-- 它的作用很单纯:路由的占位符 -->
    <router-view></router-view>
    </div>
    </template>
  5. 定义路由规则
    src/router/index.js 路由模块中,通过 routes 数组声明路由的匹配规则
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //导入需要展示的组件
    import Home from '@/components/Home.vue'
    import Movie from '@/components/Movie.vue'
    import About from '@/components/About.vue'

    // 创建路由实例的时候,写入路由规则
    const router = new VueRouter({
    routes:[
    {path:'/home',component:Home},
    {path:'/movie',component:Movie},
    {path:'/about',component:About}
    ]
    })
    这样以后,通过路由连接就可以显示对应的组件了

路由重定向

路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面。通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向

1
2
3
4
5
6
7
8
9
// 创建路由实例的时候,写入路由规则
const router = new VueRouter({
routes:[
{path:'/',redirect:'/home'},
{path:'/home',component:Home},
{path:'/movie',component:Movie},
{path:'/about',component:About}
]
})

嵌套路由

通过路由实现组件的嵌套展示,叫做嵌套路由。

下面我们来实现嵌套路由

声明子路由链接和子路由占位符

想要使用嵌套路由,那么子组件中就需要有路由链接和路由占位符,下面在 About.vue 中声明子路由链接和子路由占位符

注意在声明路由链接时,前面需要加上该组件的路由然后再跟子路由

1
2
3
4
5
6
7
8
9
10
11
<template>
<div class="about-container">
<h3>About 组件</h3>
<!-- 子级路由链接 -->
<router-link to="/about/tab1">tab1</router-link>
<router-link to="/about/tab2">tab2</router-link>
<hr />
<!-- 子级路由占位符 -->
<router-view></router-view>
</div>
</template>

声明子路由规则

src/router/index.js 路由模块中,导入需要的组件,并使用 children 属性声明子路由规则

注意子路由写的时候,path习惯不写前面的/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建路由的实例对象
const router = new VueRouter({
// routes 是一个数组,作用:定义 “hash 地址” 与 “组件” 之间的对应关系
routes: [
{ path: '/', redirect: '/home' },
// 路由规则
{ path: '/home', component: Home },
{ path: '/movie', component: Movie},
{
path: '/about',
component: About,
children: [
// 子路由规则
{ path: 'tab1', component: Tab1 },
{ path: 'tab2', component: Tab2 }
]
}
]
})

默认子路由

在上面的子路由规则写好后,我们直接点击定位到about发现子路由的组件没有显示,我们可以通过设置重定向来让它显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 创建路由的实例对象
const router = new VueRouter({
// routes 是一个数组,作用:定义 “hash 地址” 与 “组件” 之间的对应关系
routes: [
{ path: '/', redirect: '/home' },
// 路由规则
{ path: '/home', component: Home },
{ path: '/movie', component: Movie},
{
path: '/about',
component: About,
redirect: '/about/tab1',
children: [
// 子路由规则
{ path: 'tab1', component: Tab1 },
{ path: 'tab2', component: Tab2 }
]
}
]
})

除了重定向我们可以使用默认子路由来实现,如果 childre 数组中,某个路由规则 path 值为空字符串,则这条路由规则叫做“默认子路由

注意,使用默认子路由后,在写组件的子路由链接时,默认的子路由不要再加后边的路径,直接<router-link to="/about">tab1</router-link> 就好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建路由的实例对象
const router = new VueRouter({
// routes 是一个数组,作用:定义 “hash 地址” 与 “组件” 之间的对应关系
routes: [
{ path: '/', redirect: '/home' },
// 路由规则
{ path: '/home', component: Home },
{ path: '/movie', component: Movie},
{
path: '/about',
component: About,
children: [
// 子路由规则
{ path: '', component: Tab1 },
{ path: 'tab2', component: Tab2 }
]
}
]
})

动态路由匹配

如果我们需要根据不同的电影id来访问电影详情,写下如下的路由链接

1
2
3
<router-link to="/movie/1">洛基</router-link>
<router-link to="/movie/2">雷神</router-link>
<router-link to="/movie/3">复联</router-link>

为了不给每个连接都定义一个路由规则,提高路由规则的复用性,我们就需要使用动态路由匹配

动态路由指的是:把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性。

在 vue-router 中使用英文的冒号:来定义路由的参数项。通过props属性开启 props 传参,方便拿到动态参数

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建路由的实例对象
const router = new VueRouter({
// routes 是一个数组,作用:定义 “hash 地址” 与 “组件” 之间的对应关系
routes: [
// 重定向的路由规则
{ path: '/', redirect: '/home' },
// 路由规则
{ path: '/home', component: Home },
// 需求:在 Movie 组件中,希望根据 id 的值,展示对应电影的详情信息
// 可以为路由规则开启 props 传参,从而方便的拿到动态参数的值
{ path: '/movie/:mid', component: Movie, props: true },
]
})

既然需要根据不同的id来显示Movie组件的内容,那么我们就需要在movie组件中得到id的动态值

我们通过打印 Movie组件的 this 来看路由相关的信息,并且使用 props 来接收传来的动态的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="movie-container">
<!-- this.$route 是路由的“参数对象” -->
<!-- this.$router 是路由的“导航对象” -->
<h3>Movie 组件 --- {{ $route.params.mid }} --- {{ mid }}</h3>
<button @click="showThis">打印 this</button>
</div>
</template>
<script>
export default {
name: 'Movie',
// 接收 props 数据
props: ['mid'],
methods: {
showThis() {
console.log(this)
}
}
}
</script>

路由的两种参数

路径参数: 在 hash 地址中, / 后面的参数项 比如 /movie/1

查询参数: 在 hash 地址中,? 后面的参数项 比如 /movie/2?name=zs&age=20

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 注意1:在 hash 地址中, / 后面的参数项,叫做“路径参数” -->
<!-- 在路由“参数对象”中,需要使用 this.$route.params 来访问路径参数 -->

<!-- 注意2:在 hash 地址中,? 后面的参数项,叫做“查询参数” -->
<!-- 在路由“参数对象”中,需要使用 this.$route.query 来访问查询参数 -->

<!-- 注意3:在 this.$route 中,path 只是路径部分;fullPath 是完整的地址 -->
<!-- 例如: -->
<!-- /movie/2?name=zs&age=20 是 fullPath 的值 -->
<!-- /movie/2 是 path 的值 -->
<router-link to="/movie/1">洛基</router-link>
<router-link to="/movie/2?name=zs&age=20">雷神</router-link>
<router-link to="/movie/3">复联</router-link>
<router-link to="/about">关于</router-link>

导航跳转

在浏览器中,点击链接实现导航的方式,叫做声明式导航。例如:
⚫ 普通网页中点击 <a> 链接、vue 项目中点击 <router-link> 都属于声明式导航

在浏览器中,调用 API 方法实现导航的方式,叫做编程式导航。例如:
⚫ 普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航

vue-router 中编程式导航API

vue-router 提供了许多编程式导航的 API,其中最常用的导航 API 分别是:

this.$router.push(‘hash 地址’)
⚫ 跳转到指定 hash 地址,展示对应组件,并增加一条历史记录

this.$router.replace(‘hash 地址’)
⚫ 跳转到指定的 hash 地址,展示对应组件,并替换掉当前的历史记录

this.$router.go(数值 n)
⚫ 可以在浏览历史中前进和后退 n 个页面

$router.back()
⚫ 在历史记录中,后退到上一个页面

$router.forward()
⚫ 在历史记录中,前进到下一个页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
<div class="home-container">
<h3>Home 组件</h3>
<button @click="gotoLk">通过 push 跳转到“洛基”页面</button>
<button @click="gotoLk2">通过 replace 跳转到“洛基”页面</button>
<button @click="showThis">打印 this</button>
<button @click="goback">后退</button>
<!-- 在行内使用编程式导航跳转的时候,this 必须要省略,否则会报错! -->
<button @click="$router.back()">back 后退</button>
<button @click="$router.forward()">forward 前进</button>
</div>
</template>
<script>
export default {
name: 'Home',
methods: {
gotoLk() {
// 通过编程式导航 API,导航跳转到指定的页面
this.$router.push('/movie/1')
},
gotoLk2() {
this.$router.replace('/movie/1')
},
showThis() {
console.log(this)
},
goback() {
// go(-1) 表示后退一层
// 如果后退的层数超过上限,则原地不动
this.$router.go(-1)
}
}
}
</script>

导航守卫

导航守卫可以控制路由的访问权限

全局前置守卫

src/router/index.js 中使用 router.beforeEach(fn) 给路由对象设置全局前置守卫,其中 fn 为全局前置守卫的回调函数

fn回调函数有三个形参 to\from\next:

  1. to 表示将要访问的路由的信息对象
  2. from 表示将要离开的路由的信息对象
  3. next() 函数表示放行的意思

我们通过操作 to / from 就可以进行路由守卫的工作,下面例子中实现必须登录后才可以进入主页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// src/router/index.js 就是当前项目的路由模块
import Vue from 'vue'
import VueRouter from 'vue-router'
// 导入需要的组件
import Home from '@/components/Home.vue'
import Login from '@/components/Login.vue'
import Main from '@/components/Main.vue'
// 把 VueRouter 安装为 Vue 项目的插件
Vue.use(VueRouter)
// 创建路由的实例对象
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/login', component: Login },
{ path: '/main', component: Main }
]
})
// 为 router 实例对象,声明全局前置导航守卫
// 只要发生了路由的跳转,必然会触发 beforeEach 指定的 function 回调函数
router.beforeEach(function(to, from, next) {
// to 表示将要访问的路由的信息对象
// from 表示将要离开的路由的信息对象
// next() 函数表示放行的意思
// 分析:
// 1. 要拿到用户将要访问的 hash 地址
// 2. 判断 hash 地址是否等于 /main。
// 2.1 如果等于 /main,证明需要登录之后,才能访问成功
// 2.2 如果不等于 /main,则不需要登录,直接放行 next()
// 3. 如果访问的地址是 /main。则需要读取 localStorage 中的 token 值
// 3.1 如果有 token,则放行
// 3.2 如果没有 token,则强制跳转到 /login 登录页
if (to.path === '/main') {
// 要访问后台主页,需要判断是否有 token
const token = localStorage.getItem('token')
if (token) {
next()
} else {
// 没有登录,强制跳转到登录页
next('/login')
// 没有的登录,强制停留在当前页面
// next(false)
}
} else {
next()
}
})
export default router

使用vue-cil创建有路由的项目

创建的主要步骤和之前大致相同,详细可以看 vue-cil的使用

这里主要说两点和路由相关的不同的地方

  1. 创建时选择路由
  1. 使用哈希路径的路由

下面是询问你是否使用 history 形式的路由模式,我们选择 NO,我们用的是hash的(即使用 # 开头的)

创建完后,会发现项目结构和之前有一点不同 src 目录下除了 components 还多了一个 views文件夹

views 文件夹也是放组件的,通过路由切换的组件一般都放在这里