第10章 Vue Router
目录
10.1 什么是路由
10.2 Vue Router的安装
10.2.1 本地独立版本方法
10.2.2 CDN方法
10.2.3 NPM方法
10.2.4 命令行工具(Vue CLI)方法
10.3 Vue Router的基本用法
10.3.1 跳转与传参
10.3.2 配置路由
10.4 Vue Router高级应用
10.4.1 动态路由匹配
10.4.2 嵌套路由
10.4.3 编程式导航
10.4.4 命名路由
10.4.5 重定向编辑
10.4.6 路由组件props传参
10.4.7 HTML5 历史记录模式
10.5 路由钩子函数
10.5.1 全局前置钩子函数
10.5.2 全局解析钩子函数
10.5.3 全局后置钩子函数
10.5.4 某个路由的钩子函数
10.5.5 组件内的钩子函数
10.6 路由元信息
10.1 什么是路由
路由,本是一个网络工程术语,是指分组从源到目的地时,决定端到端路径的网络范围的进程。在Web前端单页面应用中,路由描述的是URL与UI之间的映射关系,这种映射是单向的,即URL变化引起UI更新(无需刷新页面)。
Vue Router 是 Vue.js 官方的路由管理器,它和 Vue.js 的核心深度集成,使构建单页面应用变得更加容易。
10.1.1 SPA与前端路由
SPA指的是一个web网站只有唯一的一个HTML页面,所有组件的展示与切换
都在唯一的一个页面内完成。
此时,不同组件之间的切换
需要通过前端路由
来实现。
结论:在 SPA 项目中,不同功能之间的切换
,要依赖于前端路由
来完成!
10.1.2 什么是前端路由
Hash地址与组件之间的对应关系
图是参考这篇文章:vue-router 路由超详细教程_vue router-CSDN博客
10.2 Vue Router的安装
10.2.1 本地独立版本方法
可通过地址“https://unpkg.com/vue-router@next”将最新版本的Vue Router库(vue-router.global.js)下载到本地(在页面上右击,在弹出的快捷菜单中选择另存为),编写本书时,最新版本是4.0.13。然后,在界面文件中引入vue-router.global.js库,示例代码如下。
<script src="js/vue-router.global.js"></script>
10.2.2 CDN方法
在界面文件中可通过CDN(Content Delivery Network,内容分发网络)引入最新版本的Vue Router库,示例代码如下。
<script src="https://unpkg.com/vue-router@next"></script>
对于生产环境,建议使用固定版本,以免因版本不同带来兼容性问题,示例代码如下。
<script src="https://unpkg.com/vue-router@4.0.13/dist/vue-router.global.js"></script>
10.2.3 NPM方法
在使用Vue.js构建大型应用时推荐使用NPM安装最新稳定版的Vue Router,因为NPM能很好地和webpack模块打包器配合使用,示例如下。
npm install vue-router@next
10.2.4 命令行工具(Vue CLI)方法
为提高单页面应用程序的开发效率,我们现在开始使用Vue CLI(Vue 脚手架)搭建Vue.js项目。
Vue CLI是一个基于Vue.js进行快速开发的完整系统,提供如下功能。
l 通过@vue/cli实现交互式项目脚手架;
l 通过@vue/cli + @vue/cli-service-global实现零配置原型开发;
l 一个运行时依赖@vue/cli-service,该依赖可升级,基于webpack构建,并带有合理的默认配置;可通过项目的配置文件进行配置;可通过插件进行扩展;
l 一个丰富的官方插件集合,集成了前端生态工具;
l 提供一套创建和管理Vue.js项目的用户界面。
Vue CLI 致力于将Vue.js生态工具基础标准化。确保各种构建工具平稳衔接,让开发者专注在撰写应用上,而不必纠结配置的问题。
1.全局安装Vue CLI
打开cmd命令行窗口,输入命令npm install -g @vue/cli全局安装Vue脚手架,输入命令vue --version查看版本(测试是否安装成功)。如果需要升级全局的 Vue CLI,在cmd命令行窗口运行npm update -g @vue/cli命令即可。
2.打开图形化界面
安装成功后,在命令行窗口,继续输入命令vue ui打开一个浏览器窗口,并以图形化界面引导至项目创建的流程。
3.创建项目--创建项目界面
4.使用VSCode打开项目
使用VSCode打开(File —> Open Folder,选择项目目录)第3步创建的项目router-demo。打开后,在Terminal终端输入npm run serve命令启动服务。
5.运行项目
在浏览器地址栏中,访问http://localhost:8080/即可运行项目router-demo。通过http://localhost:8080/访问时,打开的页面是public目录下的index.html。index.html是一个普通的html文件,让它与众不同的是“<div id="app"></div>”这句程序,下面有一行注释,构建的文件将会被自动注入,也就是说我们编写的其他的内容都将在这个div中展示。另外,整个项目只有这一个html文件,所以这是一个单页面应用,当我们打开这个应用,表面上可以看到很多页面,实际上它们都在这一个div中显示。
在main.js中,创建了一个Vue对象。该Vue对象的挂载目标是“#app”(与index.html中的id="app"对应);router代表该对象包含Vue Router,并使用项目中定义的路由(在src/router目录下的index.js文件里定义)。
10.3 Vue Router的基本用法
使用Vue Router动态加载不同组件时,需要将组件(Components)映射到路由(Routers),然后告诉Vue Router在哪里显示它们。
vue-router
是 vue.js 官方给出的路由解决方案
。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目中组件的切换。
10.3.1 路由安装
① 安装 vue-router 包
npm install vue-router@4
② 创建路由模块(vue3.0)
在 src 源代码目录下,新建 router/index.js 路由模块,并初始化如下的代码:
import { createRouter, createWebHistory } from 'vue-router'
//导入组件
import SecondView from '../views/SecondView.vue'
import ThirdView from '../views/ThirdView.vue'
//定义路由
const routes = [{path: '/first',name: 'first',//导入组件component: () => import('../views/FirstView.vue')},{path: '/second/:uname/:pwd',name: 'second',//导入组件component: SecondView},{path: '/third/:uname/post/:pwd/post/:age',name: 'third',//导入组件component: ThirdView}
]
//创建路由实例router(管理路由),传入routes配置
const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes
})
export default router
③ 导入并挂载路由模块
在 src/main.js 入口文件中,导入并挂载路由模块。示例代码如下:
import './assets/main.css'
// import Vue from 'vue'
// import App from './App.vue'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
④ 声明路由链接和占位符
在 src/App.vue 组件中,使用 vue-router 提供的 <router-link>
和 <router-view>
声明路由链接和占位符:
<template><nav><router-link to="/first?uname=chenheng&pwd=123456">第一个页面</router-link> |<router-link to="/second/chenheng1/654321">第二个页面</router-link>|<router-link to="/third/:张三/post/:654321/post/:18">第三个页面</router-link></nav><!--router-view表示路由出口,将匹配到的组件(相当于链接的页面)渲染在这里。 --><router-view/>
</template><style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;
}
nav {padding: 30px;
}
nav a {font-weight: bold;color: #2c3e50;
}
nav a.router-link-exact-active {color: #42b983;
}
</style>
10.3.2 跳转与传参
Vue Router有两种跳转,第一种是使用内置的<router-link>组件,默认渲染一个<a>标签,示例代码如下。
<router-link>组件与一般组件一样,to是一个prop,指定跳转的路径。使用<router-link>组件,在HTML5的History模式下将拦截点击,避免浏览器重新加载页面。
Vue Router的第二种跳转方式需要在JavaScript里进行,类似于window.location.href。这种方式需要使用router实例方法push或replace。
Vue Router的第二种跳转方式需要在JavaScript里进行,类似于window.location.href。这种方式需要使用router实例方法push或replace。
<template><div>第一个页面</div><button @click="goto">去第二个页面</button>
</template>
<script>
export default {methods: {goto () {// 也可以使用replace方法,与replace属性一样不会向history添加新记录this.$router.push('/MView2')}}
}
路由传参,一般有两种方式:query和params。不管哪种方式都可以是通过修改URL来实现。
(1)query传参
query传递参数的示例代码如下:
<router-link to="/?id=888&pwd=999">
通过$route.query获取路由中的参数,示例代码如下:
<h4>id:{{$route.query.id}}</h4>
<h4>pwd:{{$route.query.pwd}}</h4>
(2)params传参
在路由规则中定义参数,修改路由规则的path属性(动态匹配),示例代码如下。
{
path: '/:id/:pwd',
name: 'MView1',
component: MView1
}
<router-link to="/888/999">
通过$route.params获取路由中的参数,示例代码如下:
<h4>id:{{$route.params.id}}</h4>
<h4>pwd:{{$route.params.pwd}}</h4>
10.3.3 配置路由
路由配置,通常在前端工程项目的src/router/index.js文件中进行。首先,需要在前端工程项目的src/main.js和src/router/index.js文件中,分别导入vue和vue-router模块,并在main.js中执行use方法注册路由。
10.4 Vue Router高级应用
10.4.1 动态路由匹配
如果有多个参数,即多个冒号,则$route.params中保存为对象。例如,路由路径path为/user/:uname/:pwd,则对应的访问路径为/user/zhangsan/123456,$route.params中的对象为{ uname: 'zhangsan', pwd: '123456'}。另外,也可以使用post进行多个动态参数传递,例如,路由路径path为/user/:uname/post/:pwd/post/:age,则对应的访问路径为/user/:lisi/post/:654321/post/:18,$route.params中的对象为{ uname: 'lisi', pwd: '123456', age: '18'}。
$route路由信息对象表示当前激活的路由状态信息,每次成功导航后都将产生一个新的对象。除了$route.params外,$route对象还提供其他许多有用的信息
序 号 | 属 性 名 称 | 说 明 |
1 | $route.path | 对应当前路由的路径,如/third/:张三/post/:654321/post/:18 |
2 | $route.params | 一个key:value对象,包含了所有动态参数,如果没有参数,则是一个空对象,如{ "uname": ":张三", "pwd": ":654321", "age": ":18" } |
3 | $route.query | 一个key:value对象,表示URL查询参数。例如,/first?uname=chenheng&pwd=123456,则有$route.query.uname为chenheng。如果没有查询参数,则是空对象 |
4 | $route.hash | 当前路由的哈希值(不带#),如果没有哈希值,则为空字符串 |
5 | $route.fullPath | 完成解析后的URL,包含查询参数和哈希的完整路径 |
6 | $route.matched | 返回数组,包含当前匹配的路径中包含的所有片段所对应的配置 |
7 | $route.name | 当前路径名称 |
8 | $route.meta | 路由元信息 |
10.4.2 嵌套路由
嵌套路由,即路由的多层嵌套,也称为子路由。在实际应用中,嵌套路由相当于多级菜单,一级菜单下有二级菜单,二级菜单下有三级菜单,等等。
首先,在根组件App.vue中定义基础路由(相当于一级菜单)导航;其次,定义基础路由对应的组件;最后,完成所有嵌套路由组件的定义,并在router/index.js文件中定义嵌套路由。
如果'vue-cli-service' 不是内部或外部命令,也不是可运行的程序
npm install -g@vue/cli
详细代码:
代码目录
main.js
//main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'createApp(App).use(router).mount('#app')
router/index.js
//router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import ProductView from '../views/ProductView.vue'const routes = [{path: '/',name: 'home',component: HomeView},{path: '/about',name: 'about',component: () => import('../views/AboutView.vue')},{path: '/product',name: 'product',component: ProductView,children:[//子路由{path: '', //空子路由为基础路由的默认显示component: () => import('../views/AlldevView.vue')},{path: 'alldev', //注意这里没有'/'component: () => import('../views/AlldevView.vue')},{path: 'JavaEE',component: () => import('../views/JavaEEView.vue')},{path: 'SpringBoot',component: () => import('../views/SpringBoot.vue')}]}
]const router = createRouter({history: createWebHistory(process.env.BASE_URL),routes
})export default router
app.vue
//app.vue
<template><h1>嵌套路由</h1><nav><router-link to="/">首页</router-link> |<router-link to="/about">关于我们</router-link> |<router-link to="/product">产品介绍</router-link></nav><router-view class="my-view"> </router-view>
</template><style>#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;}nav {padding: 30px;}nav a {font-weight: bold;color: #2c3e50;}nav a.router-link-exact-active {color: #42b983;}.my-view {width: 500px;margin: 0 auto;text-indent: 2em;text-align: left;padding: 5px 10px;border: 1px dashed #42b983;}
</style>
alldevView.vue
<template><div><img alt="alldev" src="../images/091883-all.jpg" width="200" height="300"></div>
</template>
JavaEEView.vue
<template><div><img alt="javaee" src="../images/079720-javaee.jpg" width="200" height="300"></div>
</template>
ProductView.vue
<template><div><p><!--定义嵌套路由--><router-link to="/product/alldev">全栈开发</router-link> |<router-link to="/product/JavaEE">Java EE整合开发</router-link> |<router-link to="/product/SpringBoot">Spring Boot开发</router-link></p><router-view/></div>
</template>
<style scoped>p a {text-decoration: none;}
</style>
SpringBoot.vue
<template><div><img alt="springboot" src="../images/083960-springboot.jpg" width="200" height="300"></div>
</template>
10.4.3 编程式导航
除了使用内置的<router-link>组件,渲染一个<a>标签定义导航链接外,还可以通过编程调用路由(router或this.$router)的实例方法实现导航链接。
前一步
go1(){this.$router.forward()},
后退一步
back1(){this.$router.back()},
回首页
goHome(){this.$router.push('/') //字符串路由path},
看产品介绍
goProduct(){this.$router.push({ //对象path: '/product'})},
代替关于我们
repAbout(){this.$router.replace({name: 'home', //命名路由params:{uname:'123', pwd:'abc'} //传参})}
序 号 | 方 法 名 称 | 功 能 说 明 | |
1 | push() | 跳转到由参数指定的新路由地址,在历史记录中添加一条新记录 | ![]() |
2 | replace() | 跳转到由参数指定的新路由地址,替换当前的历史记录 | ![]() |
3 | go(n) | n为整数,在历史记录中向前或后退n步 | |
4 | forward() | 在历史记录中向前一步,相当于this.$router.go(1) | ![]() |
5 | back() | 在历史记录中后退一步,相当于this.$router.go(-1) | ![]() |
push()方法和replace()方法的用法相似,唯一不同的是push()方法在历史记录中添加一条新记录,replace()方法不会添加新记录,而是替换当前记录。点击返回,会跳转到上上一个页面。
上面这个小视频的看产品介绍是push模式,看产品介绍1是replace模式,我们点返回的时候,看出来两个返回是不一样的,一个是上一步,一个调回到百度首页。
push()方法和replace()方法的参数可以是字符串、对象、命名路由、带查询参数等多种形式,示例如下。
//字符串路由path
this.$router.push('/')
//对象
this.$router.push({path: '/product'})
//命名路由及params传参,params更像post,是隐性传参
this.$router.push({name: 'home', params:{uname:'123', pwd:'abc'} })
//带查询参数,/product?uname=123&pwd=abc,更像get传参,是显性传参
this.$router.push({path: '/product', query:{uname:'123', pwd:'abc'} })
this.$router.push({name: 'home', state:{uname:'123', pwd:'abc'} })
在home对应的页面可以使用history.state接收数据。
10.4.4 命名路由
{
path: '/',
name: 'home',
component: HomeView
}
<router-link :to="{name: 'home', params: {uname: '123', pwd: 'abc'}}">首页</router-link>
与编程式导航this.$router.push({name: 'home', params:{uname:'123', pwd:'abc'} })功能相同。
10.4.5 重定向![](https://i-blog.csdnimg.cn/direct/755034ea658d4122857768dba5dba03b.png)
10.4.6 路由组件props传参
10.4.7 HTML5 历史记录模式
对比 | hash 模式 | history 模式 |
---|---|---|
url 显示 | url 中带"#" | url 中不带"#" |
回车刷新(浏览器刷新按钮) | 页面正常显示 | 后端未配置则页面显示404 |
支持版本 | 支持低版本浏览器和 IE 浏览器 | HTML5 新推出的 API |
代码对比
10.5 路由钩子函数
10.5.1 全局前置钩子函数
在Vue Router中,使用router.beforeEach注册一个全局前置钩子函数(在路由跳转前执行),注册示例代码如下。
router.beforeEach(async (to, from) => {
//在ES7标准中新增了async和await关键字,作为处理异步请求的一种解决方案
if (
// 检查用户是否已登录
!isAuthenticated &&
// 避免无限重定向
to.name !== 'Login'
) {
// 将用户重定向到登录页面
return { name: 'Login' }
}
})
10.5.2 全局解析钩子函数
在Vue Router中,使用router.beforeResolve注册一个全局解析钩子函数。与router.beforeEach 类似,在每次导航时都会触发,但是确保在导航被确认之前,同时在所有组件内钩子函数和异步路由组件被解析之后,解析钩子函数就被正确调用。
router.beforeResolve(async to => {
if (to.meta.requiresCamera) {
try {
await askForCameraPermission()
} catch (error) {
if (error instanceof NotAllowedError) {
// ... 处理错误,然后取消导航
return false
} else {
//意料之外的错误,取消导航并把错误传给全局处理器
throw error
}
}
}
})
10.5.3 全局后置钩子函数
在Vue Router中,也可以使用router.afterEach注册全局后置钩子函数,该钩子函数不接收next参数,也不会改变导航本身,在跳转之后判断。对于分析、更改页面标题、声明页面等辅助功能都很有用。示例代码如下。
router.afterEach((to, from) => {
// ...
})
10.5.4 某个路由的钩子函数
10.5.5 组件内的钩子函数
const UserDetails = {
template: `...`,
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用,不能获取组件实例 `this` !因为当该钩子函数执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用。举例来说,对于一个带有动态参数的路径`/users/:id`,在`/users/1`和 `/users/2`之间跳转的时候,由于渲染同样的`UserDetails` 组件,因此组件实例会被复用,此钩子函数在此情况下也被调用。因为在这种情况发生的时候,组件已经挂载好了,该钩子函数可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}
10.6 路由元信息
有时,可能希望将任意信息附加到路由上,如过渡名称、访问路由权限等。这些工作可以通过接收属性对象的meta属性来实现,并且它可以在路由地址和导航守卫(路由钩子函数)中都能被访问到。
下面我们用一个完整的例子把路由守卫说明白,首先了解路由守卫是干什么的,就如下这个例子,我开始点击home的页签,系统提示我没有登录,然后页面切入到登录的页面,这是一个很常见的拦截器的功能,就是判断是否有登录,在vue我们就用路由守卫干这件事,所以路由守卫其实简单理解就是拦截器。这个最重要的就是router.beforeEach,代码是写在main.JS中