初识路由
提到路由(Route),一般我们会联想到网络中常见的路由器(Router),那么路由和路由器之间有什么关联呢?路由是指路由器从一个接口接收到数据,根据数据的目的地址将数据定向传送到另一个接口的行为和动作;而路由器是执行行为和动作的硬件设备,主要用于连接网络,实现不同网络之间的通信和数据传递。
根据技术的不同,Web开发中的路由分为后端路由和前端路由。
后端路由的整个过程发生在服务器端,开发者需要在服务器端程序中建立一套后端路由规则。当服务器接收到请求后,会通过路由寻找当前请求的URL地址对应的处理程序。
前端路由的整个过程发生在浏览器端,其特点是当URL地址改变时不需要向服务器发起一个加载新页面的请求,而是在维持当前页面的情况下切换页面中显示的内容。
1.后端路由
后端路由规则和处理程序都是开发人员事先编写的代码。当服务器接收到浏览器的请求后,会通过app.get()方法根据URL地址中的路径寻找对应的处理程序。
下面以Node.js环境中的Express框架中的路由为例,演示后端路由的工作原理如下图所示。
2.前端路由
下面以Vue中的路由为例,演示前端路由的工作原理。
Vue中的路由有Hash模式和HTML5模式,具体介绍如下。
① Hash模式
Hash模式的前端路由通过URL中从“#”开始的部分实现不同组件之间的切换,“#”表示Hash符,“#”后面的值称为Hash值,该值将用于进行路由匹配。Hash模式前端路由的工作原理如下图所示。
② HTML5模式
HTML5模式的URL地址与后端路由的URL地址的风格一致,可以通过URL地址中的路径进行路由匹配。HTML5模式前端路由的工作原理如下图所示。
初识Vue Router
Vue Router有多个版本,其中,Vue Router 4适用于Vue 3,而Vue Router 3适用于Vue 2。由于本书重点讲解Vue 3,所以接下来将基于Vue Router4进行讲解。
Vue Router的基本使用
路由的基本使用步骤分为以下4步。
1.定义路由组件
在src\components目录下创建Home.vue文件和About.vue文件。
<!-- Home.vue文件 -->
<template>
<div class="home-container">
<h3>Home组件</h3>
</div>
</template>
<style scoped>
.home-container {
min-height: 150px;
background-color: #f2f2f2;
padding: 15px;
}
</style>
<!-- About.vue文件 -->
<template>
<div class="about-container">
<h3>About组件</h3>
</div>
</template>
<style scoped>
.about-container {
min-height: 150px;
background-color: #f2f2f2;
padding: 15px;
}
</style>
2.定义路由链接和路由视图
使用<router-view>标签定义路由视图,该标签会被渲染成当前路由对应的组件。通过<router-link>标签定义路由链接方便在不同组件之间切换。
<template>
<div class="app-container">
<h1>App根组件</h1>
<router-link to="/home">首页</router-link>
<router-link to="/about">关于</router-link>
<hr>
<router-view></router-view>
</div>
</template>
<style scoped>
.app-container {
text-align: center;
font-size: 16px;
}
.app-container a {
padding: 10px;
color: #000;
}
.app-container a.router-link-active {
color: #fff;
background-color: #000;
}
</style>
3.创建路由模块
① 在src目录下创建router.js文件作为路由模块,并在该文件中导入路由相关函数
import { createRouter, createWebHashHistory } from 'vue-router'
② 在router.js文件中导入需要被路由控制的Home组件和About组件。
import Home from './components/Home.vue'
import About from './components/About.vue'
③ 在router.js文件中创建路由实例对象。
const router = createRouter({
history: createWebHashHistory(),
routes: [
{ path: '/home', component: Home },
{ path: '/about', component: About },
]
})
注意:
步骤②和③的组件加载方式是一次加载所有组件,除了这种方式外,还可以通过懒加载的方式动态加载组件。
④ 在router.js文件中导出路由实例对象
export default router
下面讲解如何通过懒加载的方式动态加载组件。
删除第②步的代码。将第③步的代码修改为如下代码。
const router = createRouter({
history: createWebHashHistory(),
routes: [
{ path: '/home', component: () => import('./components/Home.vue') },
{ path: '/about', component: () => import('./components/About.vue') },
]
})
4. 导入并挂载路由模块
在src\main.js文件中导入并挂载路由模块。
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router.js' // 导入路由模块
const app = createApp(App)
app.use(router) // 挂载路由模块
app.mount('#app')
执行yarn dev命令启动服务。服务启动成功后,在浏览器中访问http://127.0.0.1:5173/,使用路由后的初始页面效果、单击“首页”路由链接后的Home组件的效果、单击“关于”路由链接后的About组件的效果如下图所示。
多学一招:更改路由链接激活项的类名
在默认情况下,路由链接激活项的类名为router-link-active。如果需要更改类名,可以在创建路由实例对象时通过linkActiveClass属性设置一个类名。
const router = createRouter({
linkActiveClass: 'router-active',
……
})
路由重定向
在开发过程中,可能希望当用户访问不同的路径时,页面中显示同一个组件,这时就需要用到路由重定向。路由重定向可以使用户在访问一个URL地址时,强制跳转到另一个URL地址,从而展示特定的组件。通过路由匹配规则中的redirect属性可以指定一个新的路由地址,从而实现路由重定向。
下面演示路由重定向的使用方法。
修改src\router.js文件,实现当用户访问“/”路径时,将路由重定向到“/home”路径。
const router = createRouter({
history: createWebHashHistory(),
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: import ('./components/Home.vue') },
{ path: '/about', component: import('./components/About.vue') }
]
})
嵌套路由
嵌套路由是指通过路由实现组件的嵌套展示,它主要由页面结构决定。实际项目中的应用界面通常由多层嵌套的组件组合而成,为了使多层嵌套的组件能够通过路由访问,路由也需要具有嵌套关系,也就是在路由里面嵌套它的子路由。
下面演示嵌套路由的简单配置。
在src\router.js文件的路由匹配规则中通过children属性定义子路由匹配规则。
routes: [
{
path: '父路由路径',
component: 父组件,
children: [
{ path: '子路由路径1', component: 子组件1 },
{ path: '子路由路径2', component: 子组件2 }
]
}
]
在组件中定义子路由链接的语法格式如下。
<router-link to="/父路由路径/子路由路径"></router-link>
注意:
当使用children属性定义子路由匹配规则时,子路由的path属性前不要带“/”,否则会永远以根路径开始请求。
步骤2
创建src\components\pages\Tab1.vue文件。
<template>
<div>Tab1组件</div>
</template>
<style scoped>
div {
text-align: left;
background-color: #9dc4e5;
}
</style>
步骤3
创建src\components\pages\Tab2.vue文件。
<template>
<div>Tab2组件</div>
</template>
<style scoped>
div {
text-align: left;
background-color: #ffba00;
}
</style>
步骤4
在component\About.vue文件中添加子路由链接和子路由视图。
<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>
<style scoped>
.about-container {
min-height: 150px;
background-color: #f2f2f2;
padding: 15px;
}
.about-container a {
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
padding: 5px 10px;
color: #000;
margin: 0 5px;
}
.about-container a.router-link-active {
color: #000;
background-color: #deebf6;
}
</style>
步骤5
修改src\router.js文件,在routes中导入Tab1组件和Tab2组件,并使用children属性定义子路由匹配规则。
routes: [
{ path: '/', redirect: '/about' },
{ path: '/home', component: () => import ('./components/Home.vue') },
{
path: '/about', component: () => import('./components/About.vue'),
children: [
{ path: 'tab1', component: () => import ('./components/pages/Tab1.vue') },
{ path: 'tab2', component: () => import ('./components/pages/Tab2.vue') }]
}]
在浏览器中访问http://127.0.0.1:5173/。页面打开后,会自动重定向到About组件的路由,页面显示About组件;单击“tab1”链接,页面显示About组件中的Tab1组件;单击“tab2”链接,页面显示About组件中的Tab2组件,如下图所示。
动态路由
动态路由概述
动态路由是一种路径不固定的路由,路径中可变的部分被称为动态路径参数(Dynamic Segment),使用动态路径参数可以提高路由规则的可复用性。在Vue Router的路由路径中,使用“:参数名”的方式可以在路径中定义动态路径参数。
下面通过代码演示如何定义一个动态路由。
注意
不同动态路径参数的动态路由在进行切换时,由于它们都是指向同一组件,所以Vue不会销毁再重新创建这个组件,而是复用这个组件。如果想要在切换时进行一些操作,就需要在组件内部利用watch来监听路由的变化。
下面演示如何在About组件中监听路由的变化。
在About组件中利用watch监听路由的变化。
<script setup>
import { useRoute } from 'vue-router'
import { watch } from 'vue'
const route = useRoute()
watch(() => route.path, path => {
console.log('路由路径', path)
})
</script>
在浏览器中访问http://127.0.0.1:5173/,依次单击子路由链接“tab1”和“tab2”,监听路由变化的效果,如下图所示。
获取动态路径参数值
获取动态路径参数值的2种方式:使用$route.params获取参数值、使用props获取参数值。
使用$route.params获取参数值
新建src\components\Movie.vue文件,在该文件中定义3个路由链接和路由视图。
<template>
<div class="movie-container">
<router-link to="/movie/1">电影1</router-link>
<router-link to="/movie/2">电影2</router-link>
<router-link to="/movie/3">电影3</router-link>
<router-view></router-view>
</div>
</template>
<style>
.movie-container {
min-height: 150px;
background-color: #f2f2f2;
}
.movie-container a {
padding: 0 5px;
font-size: 18px;
border: 1px solid #ccc;
border-radius: 5px;
color: #000;
margin: 0 5px;
}
</style>
Movie组件初始页面效果、单击“电影1”链接,跳转到MovieDetails组件的效果,如下图所示。
使用props获取参数值:Vue Router允许在路由匹配规则中开启props传参。
① 修改src\components\MovieDetails.vue文件,使用props接收路由规则中匹配到的参数。
<template>
<p>电影{{ id }}页面</p>
</template>
<script setup>
const props = defineProps({
id: String
})
</script>
② 在src\router.js文件中,为“:id”路径的路由开启props传参。
{
path: ':id',
component: () => import ('./components/movieDetails.vue'),
props: true
}
命名路由
使用路由时,一般会先在routes属性中配置路由匹配规则,然后在页面中使用<router-link>的to属性跳转到指定目标地址。但这种方式存在一些弊端,例如,当目标地址比较复杂时,不便于记忆;当地址发生变化时,需要同步修改所有使用了该地址的代码,会增加开发的工作量。为此,Vue Router提供了命名路由。
在定义路由匹配规则时,使用name属性为路由匹配规则定义路由名称,即可实现命名路由。当路由匹配规则有了路由名称后,在定义路由链接或执行某些跳转操作时,可以直接通过路由名称表示相应的路由,不再需要通过路由路径表示相应的路由。
使用命名路由的语法格式如下。
{ path: '路由路径', name: '路由名称', component: 组件 }
注意
命名路由的name属性值不能重复,必须保证是唯一的
在<router-link>标签中使用命名路由时,需要动态绑定to属性的值为对象。当使用对象作为to属性的值时,to前面要加一个冒号,表示使用v-bind指令进行绑定。在对象中,通过name属性指定要跳转到的路由名称,使用params属性指定跳转时携带的路由参数,语法格式如下。
<router-link :to="{ name: 路由名称, params: { 参数名: 参数值 } }"></router-link>
使用命名路由实现单击Home组件的“跳转到MovieDetails组件”链接跳转到MovieDetails组件,并在页面中获取id值。
单击“首页”链接后的页面效果、单击“跳转到MovieDetails组件”链接后的页面效果,如下图所示。
编程式导航
在Vue中,页面有两种导航方式,分别是声明式导航和编程式导航。其中,使用<router-link>标签定义导航链接的方式属于声明式导航。而编程式导航是先通过useRouter()函数获取全局路由实例,然后通过调用全局路由实例实现导航。
Vue Router提供了useRouter()函数,使用它可以获取全局路由实例,该全局路由实例中提供了常用的push()方法、replace()方法和go()方法,获取全局路由实例的示例代码如下。
1.push()方法
push()方法会向历史记录中添加一个新的记录,以编程方式导航到一个新的URL。当用户单击浏览器后退按钮时,会回到之前的URL。push()方法的参数可以是一个字符串路径,或者一个描述地址的对象,示例代码如下。
如果在参数的对象中提供了path,则params会被忽略。为了传递参数,需要提供路由的name或者手动拼接带有参数的path,示例代码如下。
使用push()方法实现单击Home组件的“跳转到MovieDetails组件”链接跳转到MovieDetails组件,并在页面中获取id值。
2.replace()方法
replace()方法与push()方法类似,都是以编程方式导航到一个新的URL。replace()方法在导航后不会向历史记录中添加新的记录,而是会替换历史记录中的当前记录。
在声明式导航中,为<router-link>标签添加replace属性也能实现与replace()方法类似的效果,示例代码如下。
// 编程式导航
router.replace({ path: '/user' })
<!-- 声明式导航 -->
<router-link :to="{ path: '/user' }" replace></router-link>
3.go()方法
go()方法用于实现前进或后退的效果,其参数表示历史记录中前进或后退的步数,类似于window.history.go(),相应的地址栏也会发生改变。
- go(1)表示前进一条记录。
- go(-1)表示后退一条记录。
使用go()方法实现单击MovieDetails组件的“后退”按钮后返回到Home组件的效果。
先单击“首页”链接切换到首页,然后单击“跳转到MovieDetails组件”,查看添加了“后退”按钮的页面效果,如下图所示。
导航守卫
导航守卫用于控制路由的访问权限。例如,访问后台主页时,需要用户处于已登录状态,如果没有登录,则跳转到登录页面。用户在登录页面进行登录操作,若登录成功,则跳转到后台主页;若登录失败,则返回登录页面。
在登录功能中使用导航守卫的效果,如下图所示。
导航守卫主要分为全局导航守卫、导航独享守卫和组件导航守卫。
下面讲解全局导航守卫的定义与使用。
全局导航守卫会拦截每个路由规则,从而对每个路由进行访问权限的控制,定义全局导航守卫的示例代码如下。
每个全局导航守卫方法中接收3个形参:to、from和next。
- to:表示目标路由对象;
- from:表示当前导航正要离开的路由对象;
- next:next为函数,如果不接收next()函数,则默认允许用户访问每一个路由;如果接收了next()函数,则必须调用next()函数,否则不允许用户访问任何一个路由。
注意
next()函数具有3种调用方式,分别为next()、next(false)和next('/'),其中,next()表示执行下一个钩子函数;next(false)表示强制停留在当前页面;next('/')表示跳转到其他地址。
使用全局导航守卫实现当进入MovieDetails组件时,判断当前用户是否登录,如果没有登录则跳转到登录页面,如果已登录则跳转到电影详情页面。
先切换到首页,然后单击“跳转到MovieDetails组件”链接,会显示登录页面,说明全局导航守卫拦截成功,如下图所示。