1. 前端路由的概念与原理
1.1 什么是路由
路由(router) 就是对应关系。
1.2 SPA 与前端路由
SPA
指的是一个 web
网站只有唯一的一个 HTML
页面,所有组件的展示与切换都在这唯一的一个页面内完成。
此时,不同组件之间的切换需要通过前端路由来实现。
结论:在 SPA
项目中,不同功能之间的切换,要依赖于前端路由来完成!
1.3 什么是前端路由
通俗易懂的概念:Hash
地址与组件之间的对应关系。
也就是,Hash
地址发生变化时,就会跳转到其对应的组件。
1.4 前端路由的工作方式
-
用户点击了页面上的路由链接
-
导致了
URL
地址栏中的Hash
值发生了变化 -
前端路由监听了到
Hash
地址的变化 -
前端路由把当前
Hash
地址对应的组件渲染都浏览器中
结论:前端路由,指的是 Hash
地址与组件之间的对应关系
1.5 实现简易的前端路由
通过 <component>
标签的 is
属性动态渲染组件。示例代码如下:
html
<template>
<div id="app">
<h1>主页</h1>
<component :is="comName"></component>
</div>
</template>
<script>
import LeftView from '@/components/LeftView.vue'
import RightView from '@/components/RightView.vue'
export default {
components: {
LeftView,
RightView
},
data () {
return {
comName: RightView
}
}
}
</script>
在 App.vue
组件中,为 <a>
链接添加对应的 hash
值:
html
<a href="#/left">Left</a>
<hr>
<a href="#/right">Right</a>
在 created
生命周期函数中,监听浏览器地址栏中 hash
地址的变化,动态切换要展示的组件的名称:
js
created () {
window.onhashchange = () => {
switch (location.hash) {
case '#/left':
this.comName = 'LeftView'
break
case '#/right':
this.comName = 'RightView'
}
}
}
注意:
window
后面跟的函数必须为箭头函数,箭头函数的this
指向为vue
实例,function
函数的this
指向为window
location.hash
可以获取到URL
中的锚点部分,即:URL
中#
符号及其后面的部分
关于动态组件,具体可以到我之前的文章 06动态组件插槽自定义指令 了解。
2. vue-router 的基本用法
2.1 什么是 vue-router
vue-router
是 vue.js
官方给出的路由解决方案。它只能结合 vue
项目进行使用,能够轻松的管理 SPA
项目中组件的切换。
vue-router
的官方文档地址:router.vuejs.org/zh/
2.2 vue-router 安装和配置的步骤
-
安装
vue-router
包bashnpm i vue-router@3.5.2 -S
也可以在创建
vue
项目时勾选vue-router
选项。 -
创建路由模块
在
src
源代码目录下,新建router/index.js
路由模块,并初始化如下的代码:js// 1. 导入 vue 和 vueRouter 模块 import vue from 'vue' import VueRouter from 'vue-router' // 2. 将 vueRouter 作为 vue 的插件 vue.use(VueRouter) // 3. 创建路由实例对象 const router = new VueRouter() // 4. 对外共享 export default router
-
导入并挂载路由模块
在
src/main.js
入口文件中,导入并挂载路由模块。示例代码如下:jsimport Vue from 'vue' import App from './App.vue' // 1. 导入路由 import router from '@/router' new Vue({ render: h => h(App), router: router // 键和值相同,可以简化只写一个 router }).$mount('#app')
问题: 导入路由时,为什么我们只导入到文件夹,没导入其
index.js
文件?回答: 导入时会自动查找其文件夹是否有
index.js
文件,有的话就自动导入index.js
文件。当然,路径写完整也是没有问题的。 -
声明路由链接和占位符
在
App.vue
组件中,使用vue-router
提供的<router-link>
和<router-view>
声明路由链接和占位符:html<template> <div id="app"> <h1>主页</h1> <!-- 1. 定义路由链接 --> <router-link to="/left">Left</router-link> <hr> <router-link to="/right">Right</router-link> <!-- 2. 定义路由的占位符 --> <router-view></router-view> </div> </template>
注意:
<router-link>
相当于<a>
<router-link>
的to
属性 相当于<a>
的href
属性,注意to
属性地址直接从/
开始写<router-view>
相当于动态组件的<component>
,同样起到占位效果
-
声明路由的匹配规则
在
src/router/index.js
路由模块中,通过routes
数组声明路由的匹配规则。示例代码如下:jsimport vue from 'vue' import VueRouter from 'vue-router' // 1. 导入使用的组件 import Left from '@/components/LeftView.vue' import Right from '@/components/RightView.vue' vue.use(VueRouter) // 2. 创建路由实例对象 const router = new VueRouter( { // 3. 匹配规则 routes: [ { path: '/left', component: Left }, { path: '/right', component: Right } ] } ) export default router
注意:匹配规则就是跳转到
path
所在的地址,就展示component
组件内容
3. vue-router 的常见用法
3.1 路由重定向
路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面。常用在根路径显示首页组件。
通过路由规则的 redirect
属性,指定一个新的路由地址,可以很方便地设置路由的重定向:
js
const router = new VueRouter(
{
// 3. 匹配规则
routes: [
// 当用户访问 / 时,通过 redirect 属性跳转到 /right 对应的路由规则
{ path: '/', redirect: '/right' },
{ path: '/left', component: Left },
{ path: '/right', component: Right }
]
}
)
我们有个疑惑,正常匹配不就行了吗,为什么要有重定向?或者说重定向和正常匹配的差别在哪里?
正常匹配:
js
{ path: '/', component: Right }
重定向:
js
{ path: '/', redirect: '/right' }
两者都能实现在 URL
输入 /
的情况下,显示 Right
组件。
不同的是:
-
正常匹配,在
URL
输入/
,跳转到/
路径,直接显示Right
组件 -
重定向,在
URL
输入/
,无法跳转到/
路径,跳转的是重定向写的路径/right
,所以会先找该路径的匹配规则,即{ path: '/right', component: Right }
,然后再根据该规则显示Right
组件
3.2 嵌套路由
3.2.1 定义
通过路由实现组件的嵌套展示,叫做嵌套路由。
我们刚才的路由,大概就是下图这个结构,点击父级路由链接显示其模板内容:
而嵌套路由,大概就是下图这个结构,父级模板内容中又有子级路由链接,点击子级路由链接显示子级模板内容:
3.2.2 实现
-
声明子路由链接和子路由占位符
在
LeftView.vue
组件中,声明movie1、movie2、movie3
的子路由链接以及子路由占位符。示例代码如下:html<div class="left-content"> <h1>左组件</h1> <router-link to="/left/movie1">电影1</router-link> <router-link to="/left/movie2">电影2</router-link> <router-link to="/left/movie3">电影3</router-link> <router-view></router-view> </div>
-
通过
children
属性声明子路由规则在
src/router/index.js
路由模块中,导入需要的组件,并使用children
属性声明子路由规则:js// 3. 匹配规则 routes: [ // 当用户访问 / 时,通过 redirect 属性跳转到 /right 对应的路由规则 { path: '/', redirect: '/right' }, { path: '/left', component: Left, children: [ { path: 'movie1', component: Movie1 }, { path: 'movie2', component: Movie2 }, { path: 'movie3', component: Movie3 } ] }, { path: '/right', component: Right } ]
注意:
- 在
LeftView.vue
组件,定义的子组件链接,path
需要写全 - 而在路由规则里面,
chidren
数组中的path
,可以和父组件的path
拼接,所以children
数组里的path
会更简洁
- 在
3.3 动态路由
3.3.1 提出的背景
路由链接:
html
<router-link to="/movie/1">电影1</router-link>
<router-link to="/movie/2">电影2</router-link>
<router-link to="/movie/3">电影3</router-link>
匹配规则:
js
{ path: '/movie/1', component: Movie },
{ path: '/movie/2', component: Movie },
{ path: '/movie/3', component: Movie }
缺点:这样写虽然可以,但路由规则的复用性差。所以,我们提出了动态路由。
3.3.2 动态路由的概念
动态路由指的是:把 Hash
地址中可变的部分定义为参数项,从而提高路由规则的复用性。
在 vue-router
中使用 :
来定义路由的参数项。
路由规则合并前:
js
{ path: '/movie/1', component: Movie },
{ path: '/movie/2', component: Movie },
{ path: '/movie/3', component: Movie }
路由规则合并后:
js
// 动态路由中,其 : 后面为动态参数
{ path: '/movie/:id', component: Movie }
将多个规则合为一个,提高了路由规则的复用性。
3.3.3 $route.params 参数对象
在动态路由渲染出来的组件中,可以使用 this.$route.params
对象访问到动态匹配的参数值对象。
如果要使用我们刚刚的动态参数 id
,可以这样用:
html
<h3>这是 movie {{this.$route.params.id}}</h3>
并且 this
可以省略:
html
<h3>这是 movie {{$route.params.id}}</h3>
3.3.4 使用 props 接收路由参数
为了简化路由参数的获取形式,vue-router
允许在路由规则中开启 props
传参。示例代码如下:
匹配规则:
js
{ path: 'movie/:id', component: Movie, props: true }
使用其参数的组件:
html
<template>
<div class="movie1">
<h3>这是 movie {{id}}</h3>
</div>
</template>
<script>
export default {
props: ['id']
}
</script>
3.4 声明式导航 & 编程式导航
3.4.1 定义
在浏览器中,点击链接实现导航的方式,叫做声明式导航。例如:
- 普通网页中点击
<a>
链接、vue
项目中点击<router-link>
都属于声明式导航
在浏览器中,调用 API
方法实现导航的方式,叫做编程式导航。例如:
- 普通网页中调用
location.href
跳转到新页面的方式,属于编程式导航
3.4.2 vue-router 中的编程式导航 API
vue-router
提供了许多编程式导航的 API
,其中最常用的导航 API
分别是:
- this.$router.push('hash 地址')
- this.$router.replace('hash 地址')
- this.$router.go(数值 n)
1. this.$router.push('hash 地址')
调用 this.$router.push()
方法,可以跳转到指定的 hash
地址,从而展示对应的组件页面。示例代码如下:
html
<template>
<div id="app">
<button @click="gotoMovie">跳转到电影 2</button>
</div>
</template>
<script>
export default {
methods: {
gotoMovie () {
this.$router.push('./left/movie/2')
}
}
}
注意:这个定义跳转的按钮只能点一次,点两次 URL
会叠加
2. this.$router.replace('hash 地址')
调用 this.$router.replace()
方法,可以跳转到指定的 hash
地址,从而展示对应的组件页面。
html
<template>
<div id="app">
<button @click="gotoMovie">跳转到电影 2</button>
</div>
</template>
<script>
export default {
methods: {
gotoMovie () {
this.$router.replace('./left/movie/2')
}
}
}
push
和 replace
的区别:
push
会增加一条历史记录replace
不会增加历史记录,而是替换掉当前的历史记录
但两者都不能重复按两次,否则 URL
会叠加。
3. this.$router.go(数值 n)
调用 this.$router.go()
方法,可以在浏览历史中前进和后退。示例代码如下:
html
<template>
<div id="app">
<h1>主页</h1>
<button @click="goBack">后退</button>
</div>
</template>
<script>
export default {
methods: {
goBack () {
this.$router.go(-1)
}
}
}
</script>
4. $router.go 的简化用法
在实际开发中,一般只会前进和后退一层页面。因此 vue-router
提供了如下两个便捷方法:
-
$router.back()
:在历史记录中,后退到上一个页面 -
$router.forward()
:在历史记录中,前进到下一个页面
3.5 导航守卫
3.5.1 工作原理
无导航守卫时:
导航守卫可以控制路由的访问权限。示意图如下:
其实相当于一个中间件,可以进行检验。
3.5.2 全局前置守卫
每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行访问权限的控制:
js
// 创建路由实例对象
const router = new VueRouter({ ... })
// 调用实例对象的 beforeEach 方法,即可声明 "全局前置守卫"
// 每次路由跳转时,就会触发 fn 这个回调函数
router.beforeEach(fn)
全局前置守卫的回调函数中接收 3 个形参,格式为:
js
router.beforeEach((to, from, next) => {
// 回调函数提
})
参数说明:
- to:将要访问的路由信息对象
- from:将要离开的路由信息对象
- next:函数,调用
next()
表示放行,允许这次路由导航
3.5.3 next 函数的 3 种调用方式
参考示意图,分析 next
函数的 3 种调用方式最终导致的结果:
-
当前用户拥有后台主页的访问权限,直接放行:
next()
-
当前用户没有后台主页的访问权限,强制其跳转到登录页面:
next('/login')
-
当前用户没有后台主页的访问权限,不允许跳转到后台主页:
next(false)
没有访问权限,有强制跳转到其他页面 和返回原来页面两种方案,这里代码写的是强制跳转其他页面的方案。
前两种情况代码编写,大致如下:
js
router.beforeEach(function(to, from, next) {
if (to.path == '/main'){
const token = localStorage.getItem('token')
if (token) {
next() // 访问的是后台主页,且有 token 的值
} else {
next('/login') // 访问后台主页,但是没有 token 的值(这里采用直接跳转的方案)
}
} else {
next() // 访问的不是后台主页,直接放行
}
})
如有错误,敬请指正,欢迎交流🤝,谢谢♪(・ω・)ノ