Vue 系列笔记第六篇。本文参考:>> 黑马程序员 Vue 全套视频教程
系列文章阅读
>> Vue「一」—— 前端工程化 、webpack 的基本使用及常用配置
>> Vue「二」—— vue 基本使用 、vue 指令 、vue 过滤器
>> Vue「三」—— vue 侦听器、vue 计算属性、vue-cli、vue 组件
>> Vue「四」—— 组件生命周期、数据共享
>> Vue「五」—— 动态组件、插槽、自定义指令
>> Vue「六」—— 前端路由、vue-router
首先,什么是路由?
路由的概念来源于服务端,在服务端中路由描述的是 URL 与处理函数之间的映射关系。简单来说,路由指的就是针对不同请求的 URL,处理不同的业务逻辑。
而什么又是前端路由?
历史由来就不讲了,热榜文章都有,直接来点干货。
如今热门的单页面应用程序 SPA 中,不同页面间的切换、数据交互是无刷新的,不会每次都发起服务器请求,减少了与服务器请求和响应时间,极大提高了用户体验。
页面间无刷新切换是靠什么实现的呢?那就是 前端路由 。
看看下面这个简陋版的后台管理系统案例,这就是一个单页面应用程序。尽管页面进行了多次切换,但是页面并没有进行重载,或者说的准确点,没有向服务器提交请求获取新页面,仅仅是完成了不同组件间的切换。
那前端路由起到了什么作用呢,可以看到在页面切换时,变换的只有 localhost:8080/
后面的部分,这部分我们姑且称之为 Hash 地址,可利用 window.location.hash
来获取。在这里,前端路由就是不同 Hash 地址与不同组件的对应关系,根据路由对应关系来完成不同组件之间的切换。
而要弄懂为什么没有发起服务器请求,你首先要明白 hash (#)的机理
最重要一点,HTTP 请求不包括 hash
比如,你要访问 http://localhost:8080/#/login
,但是实际上浏览器发出的请求是:
这也就意味着,当页面 Hash 地址变化时,并不会引起浏览器发出服务器请求。
这部分存在的意义仅仅是用来指导浏览器行为的,比如说当 Hash 地址为 #/login
时,浏览器就在指定位置显示登录组件。
还有一点,改变 hash 会增加浏览器访问历史
根据这一点,我们就可以实现根据访问历史前进后退。例如,利用 API $router.back()
可以实现后退到前一个页面的效果。
前端路由的工作方式
① 用户点击了页面上的路由链接,导致了 URL 地址栏中的 Hash 值发生了变化;
② 前端路由监听了到 Hash 地址的变化;
③ 前端路由把当前 Hash 地址对应的组件 渲染都浏览器中。
这种意义上来说,前端路由就是 Hash 地址与组件之间的对应关系
那么,怎么创建前端路由呢 ?我们来手动模拟一下。
给定三个链接,首页、电影、关于,点击对应链接,则在页面中动态渲染出对应组件。
<template>
<div class="app-container">
<h1>App 根组件h1>
<a href="#/home">首页a>
<a href="#/movie">电影a>
<a href="#/about">关于a>
<hr />
<component :is="comName">component>
div>
template>
我们怎么操作呢?首先要绑定事件 window.onhashchange
当前页面 Hash 值变化时触发,再利用 location.hash
获取 Hash 值。根据获取的不同 Hash 值来给动态组件指定名称。
data() {
return {
comName: "Home"
}
},
created() {
window.onhashchange = () => {
switch (location.hash) {
case "#/home":
this.comName = "Home"
break
case "#/movie":
this.comName = "Movie"
break
case "#/about":
this.comName = "About"
break
}
}
},
但是,在实际中我们并不会去手动配置前端,而是去利用一些第三方库,如下面即将介绍的 vue-router 。
vue-router 是 vue.js 官方给出的 路由解决方案 。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目中组件的切换。
官方文档:https://router.vuejs.org/zh/
使用 vue-router 的五部曲
① 安装 vue-router 包
② 创建路由模块
③ 导入并挂载路由模块
④ 声明路由链接和占位符
⑤ 声明路由的匹配规则
1. 安装 vue-router 包
npm install vue-router
2. 创建路由模块
在 src 源代码目录下,新建 router/index.js 路由模块,并初始化如下的代码:
import Vue from 'vue'
import VueRouter from 'vue-router'
// 调用 Vue.use 函数,把 VueRouter 安装为 Vue 插件
Vue.use(VueRouter)
// 创建路由的实例
const router = new VueRouter()
// 向外共享路由的实例对象
export default router
3. 导入并挂载路由模块
如果想在 Vue 项目中使用路由,就必须在 src/main.js 入口文件中,导入并挂载路由实例。
import Vue from 'vue'
import App from './App.vue'
// 导入路由模块,拿到路由的实例对象
import router from '@/router/index.js'
new Vue({
render: h => h(App),
// 路由实例对象
// 简写 router: router
router
}).$mount('#app')
4. 声明路由链接和占位符
在 src/App.vue 组件中,使用 vue-router 提供的
和
声明路由链接和占位符。
<template>
<div class="app-container">
<router-link to="/home">首页router-link>
<router-link to="/movie">电影router-link>
<router-link to="/about">关于router-link>
<router-view>router-view>
div>
template>
5. 声明路由的匹配规则
在 src/router/index.js 路由模块中,通过 routes 数组 声明路由的匹配规则。
const router = new VueRouter({
// 定义 Hash 与组件之间的对应关系
// 路由规则
routes: [
{ path: '/home', component: Home },
{ path: '/movie', component: Movie },
{ path: '/about', component: About },
]
})
路由重定向 指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面。
通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向。
const router = new VueRouter({
routes: [
// 当访问 / 时,通过 redirect 属性直接跳转到 /home 对应的路由规则
{ path: '/', redirect: '/home' },
// 路由规则
{ path: '/home', component: Home },
{ path: '/movie', component: Movie },
{ path: '/about', component: About },
]
})
这样可以使得,在开始打开页面时不至于显示空白,而是直接跳转到 home 首页。
通过路由实现组件的嵌套展示,叫做 嵌套路由 。也就是在由路由显示的组件中,还有含有路由可以显示再下一层的组件,即大家常说的套娃。
> 声明子路由链接和子路由占位符
如上图,在 About.vue 组件中,声明 tab1 和 tab2 的子路由链接以及子路由占位符,可以这样操作:
<template>
<div class="about-container">
<h3>About 组件h3>
<router-link to="/about/tab1">tab1router-link>
<router-link to="/about/tab2">tab2router-link>
<hr />
<router-view>router-view>
div>
template>
仅仅是声明子路由链接和占位符还不算完,还要引入 子路由规则 。
> 通过 children 属性声明子路由规则
在 src/router/index.js 路由模块中,导入需要的组件,并使用 children 属性 声明子路由规则:
const router = new VueRouter({
routes: [
{
path: '/about', // 声明父级路由规则
component: About,
children: [ // 嵌套声明子路由规则
{ path: 'tab1', component: Tab1 }, // about/tab1
{ path: 'tab2', component: Tab2 } // about/tab2
]
}
]
})
> 默认子路由
当打开 About 组件时,可以直接显示出某个子路由,而不是不做显示。这时,除了设置路由重定向 redirect 属性,也可以通过 默认子路由 来解决。默认子路由具体为,如果在 children 数组中某个路由规则 path: ''
,则称该路由规则为默认子路由。
children: [
{ path: '', component: Tab1 }, // 默认显示 Tab1
{ path: 'tab2', component: Tab2 } // about/tab2
]
如果我们有这样一个需求,点击下面的链接,都会跳转到 Movie 组件,哪么怎么办?你要一条条配置路由规则嘛?这里只是给出了 3 个链接,如果是 300 个、3000 个呢?当然不是逐条配置路由规则,我们可以使用 动态路由匹配 。
<router-link to="/movie/1">教父三部曲router-link>
<router-link to="/movie/2">闻香识女人router-link>
<router-link to="/movie/3">西西里的美丽传说router-link>
动态路由指的是,把 Hash 地址中可变的部分定义为 参数项 ,从而提高路由规则的复用性。
在 vue-router 中,可以使用英文的冒号 :
来定义路由的参数项。这样就避免了逐条配置的麻烦。
{ path: '/movie/:id', component: Movie },
> $route.params 参数对象
如果除此之外,我们还想获得该参数项具体参数值(如 ".movie/1"
后面参数值 1
),根据这个参数值展示相应的内容,应该怎么办?
在动态路由渲染出来的组件中,可以通过 this.$route 访问路由的参数对象:
可以看到,其中 this.$route.params 中包含有我们需要的参数值 id ,获取该 id 即可。
这种获取参数值的方式是比较传统方式,比较复杂,不是很推荐。可以使用下面开始 props 来进行传参。
> 使用 props 接收路由参数
为了简化路由参数的获取形式,vue-router 允许在路由规则中 开启 props 传参 。
在创建实例时,开启 props 传参:
const router = new VueRouter({
routes: [
{ path: '/movie/:id', component: Movie, props: true }, // 开启 props 传参
]
})
在 Movie 组件中通过 props 接收参数:
export default {
name: "Movie",
props: ["id"], // 接收路由规则中的匹配到的参数
}
在浏览器中,点击链接 实现导航的方式,叫做 声明式导航 。
例如:普通网页中 链接 、vue 项目中的
在浏览器中,调用 API 方法 实现导航的方式,叫做 编程式导航 。
例如:普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航。
> vue-router 中编程式导航 API
vue-router 提供了许多编程式导航的 API,其中最常用的导航 API 分别是:
API | 描述 |
---|---|
this.$router.push('hash 地址') |
跳转到指定 hash 地址,并 增加 一条记录 |
this.$router.replace('hash 地址') |
跳转到指定 hash 地址,并且 替换 当前历史记录 |
this.$router.go(n) |
实现浏览历史前进后退,如 n=-1 退回前一个页面 |
this.$router.back() |
在历史记录中,后退到上一个页面 |
this.$router.forward() |
在历史记录中,前进到下一个页面 |
<button @click="$router.back()">后退button>
导航守卫 可以控制路由的访问权限。可以参考此图:
> 全局前置守卫
每次发生路由的 导航跳转 时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行 访问权限 控制。
怎么创建全局前置守卫呢 ?这需要我们在创建路由实例时进行定义函数 router.beforeEach
,如下:
// 创建路由的实例
const router = new VueRouter({...})
// 声明全局前置导航守卫
router.beforeEach((to, from, next) => {
// to 表示将要访问的路由的信息对象
// from 表示将要离开的路由的信息对象
// next 是函数,调用 next 表示允许此次导航
})
这里的 next 函数有如下三种调用方式:
next()
next('/login')
跳转至登录页面next(false)
参考下图来理解一下这三种调用方式:
例如,可以通过下面这段代码来控制后台访问权限
// 声明全局前置导航守卫
router.beforeEach((to, from, next) => {
if (to.path === "/main") {
// 判断是否有 token,没有 token 则转登录页面
const token = localStorage.getItem("token")
token ? next() : next("/login")
}
else // 没有访问后台页面,直接放行
next()
})