1. 路由 - 是什么
序言: 看完 w_y小前端 的这篇文章, 相信对路由_路由导航守卫有一定的理解啦
下面有三种映射关系:
-
设备和 ip 的映射关系
-
接口和服务的映射关系
-
路径和组件的映射关系
- 什么是
路由?- 路由是一种映射关系
- Vue中的
路由是什么?- 路径和组件的映射关系
2. 路由 - 为何用
线上使用实例: 网易云音乐 (老网抑云了...)
- 单页面应用(
SPA): 所有功能在一个html页面上实现 - 前端路由作用: 实现业务场景切换 优点:
- 整体不刷新页面,用户体验更好
- 数据传递容易, 开发效率高
缺点:
- 开发成本高(需要学习专门知识)
- 首次加载会比较慢一点。不利于
seo
3. 路由 - Vue-Router 介绍
vue-router模块包
它和 Vue.js 深度集成
可以定义 - 视图表(映射规则)
模块化的
提供2个内置全局组件
声明式导航自动激活的 CSS class 的链接
...
Vue中如何实现路由?
集成vue-router 模块包
4. Vue 路由 - 组件分类
.vue文件分2类, 一个是页面组件, 一个是复用组件
- 页面组件 : 页面展示 - 配合路由用
- 复用组件 : 展示数据/常用于复用
总结: views下的页面组件, 配合路由切换, components下的一般引入到views下的vue中复用展示数据
5. Vue 路由 - Vue-Router 使用
App.vue
<template>
<div>
<div class="footer_wrap">
<a href="#/find">发现音乐</a>
<a href="#/my">我的音乐</a>
<a href="#/part">朋友</a>
</div>
<div class="top">
</div>
</div>
</template>
<script>
export default {};
</script>
<style scoped>
.footer_wrap {
position: fixed;
left: 0;
top: 0;
display: flex;
width: 100%;
text-align: center;
background-color: #333;
color: #ccc;
}
.footer_wrap a {
flex: 1;
text-decoration: none;
padding: 20px 0;
line-height: 20px;
background-color: #333;
color: #ccc;
border: 1px solid black;
}
.footer_wrap a:hover {
background-color: #555;
}
.top {
padding-top: 62px;
}
</style>
- 安装
yarn add vue-router
- 导入路由
// 1. 引入 VueRouter
import VueRouter from 'vue-router'
- 使用路由插件
// 2. 使用 Vue.use 将构造函数 VueRouter 加载到 Vue上
// Vue.use() 函数就是在装插件
// 这一步 VueRouter 注册了两个全局组件: RouterLink RouterView
Vue.use(VueRouter)
- 创建路由规则数组
// 3. 配置路由规则 -- 重点学习的内容, 规则的属性非常重要
let routes = [
{
// 重定向: 当用户访问 / 时, 强制跳到 /find
path: '/', // 用户访问的路径
redirect: '/find' // 强制跳转的路径
},
// 一个对象表示一条映射规则, 请求什么路径 切换什么组件
{
// 可以给每一条路由规则定义 name , 将来可以用于编程式导航跳转
// 建议大家写路由规则时都写上 name
// name 不需要斜杠开头
name: 'Find',
path: '/find',
// component 是配置规则定的属性名, 咱们不能随便改
// Cannot read properties of undefined (reading '$createElement')
component: Find,
children: [
// 一个对象表示一条子路由规则
// 希望用户访问 /find/recommend
{
// 子路中不要加 / 开头, 匹配的是一级路由拼接二级路由的效果
// 如果二级没有写/,需要跟一级拼接,如果二级写了/ 那就跟一级path没有关系
// /find/recommend
// path: '/recommend', // 如果不小心写了斜杠开头, 就会直接匹配二级路由的路径并切换组件, 但是没有了高亮效果
path: 'recommend',
component: Recommend,
},
{
path: 'ranking',
component: Ranking
},
{
path: 'songlist',
component: SongList
},
]
},
{
name: 'My',
path: '/my',
component: My
},
{
name: 'Part',
// 用来匹配 query 传参或无传参的情况
path: '/part',
component: Part
},
{
// 定义接收的参数, 动态路径传参
// 加了冒号 name 就是参数
// 一定不要忘了加 : , 如果忘了就变成了 /part/name
// name 最终会被放到 $route.params 中作为 name 属性传递给目标组件
// 有些论坛会用年月日来作为路径参数查询不同时间的帖子 /2020/08/12/article
path: '/part/:name/:age',
component: Part
},
// 在配置规则的最底部写通配符 * 匹配所有路径
// 兜底的规则, 当用户访问的路径都不匹配时, 就跳到这条规则
{
path: '*',
component: NotFound
}
]
记得引入路由相应的组件
- 创建路由对象 - 传入规则
// 4. 根据规则创建路由对象
let router = new VueRouter({
// routes : routes
routes,
// 设置路由模式, 取值范围: hash / history
// hash: 带井号的路径
// history: 不带#号的路径, 但是需要后端配合
mode: 'history'
})
- 关联到vue实例
// 5. 将路由对象注入到 new Vue 创建的实例中
new Vue({
render: h => h(App),
router
}).$mount('#app')
- 使用 router-view 挂载点
<div class="top">
<router-view></router-view>
</div>
总结:
- 下载路由模块, 编写对应规则注入到vue实例上, 使用router-view挂载点显示切换的路由
- 一切都围绕着hash值变化为准, 切换url上hash值, 开始匹配规则, 对象组件展示到 router-view位置
6. Vue 路由 - 声明式导航_跳转
可用全局组件router-link来替代 a标签
- vue-router提供了一个全局组件 router-link
- router-link实质上最终会渲染成a链接 to属性等价于提供 href属性(to无需#)
- router-link提供了声明式导航高亮的功能(自带类名)
<template>
<div>
<div class="footer_wrap">
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/part">朋友</router-link>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {};
</script>
<style scoped>
/* 省略了 其他样式 */
.footer_wrap .router-link-active{
color: white;
background: black;
}
</style>
总结 : 链接导航, 用 router-link 配合 to, 实现点击切换路由
7. Vue 路由 - 声明式导航_传参
概念
在跳转路由时, 可以给路由对应的组件内传值
基本用法
在router-link上的to属性传值, 语法格式如下
- /path?参数名=值&参数名2=值2
- /path/值 – 需要路由对象提前配置 path: “/path/参数名”
<template>
<div>
<div class="footer_wrap">
<!-- <a href="#/find">发现音乐</a>
<a href="#/my">我的音乐</a>
<a href="#/part">朋友</a> -->
<!-- router-link 写的就是声明式导航 -->
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/part?name=砂糖橘&age=18">朋友 - query传参</router-link>
<router-link to="/part/砂糖橘/18">朋友 - params传参</router-link>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
对应页面组件接收传递过来的值
- $route.query.参数名
- $route.params.参数名
- 组件中准备接收路由上传递的参数和值
<template>
<div>
伙伴
<!-- $route : 路由参数对象, 用于接收参数 -->
<p>接收到了 query 参数: {{ $route.query.name }} --- {{ $route.query.age}}</p>
<p>接收到了 params 参数: {{ $route.params.name }} --- {{ $route.params.age }}</p>
</div>
</template>
- 路由定义
{
name: 'Part',
// 用来匹配 query 传参或无传参的情况
path: '/part',
component: Part
},
{
// 定义接收的参数, 动态路径传参
// 加了冒号 name 就是参数
// 一定不要忘了加 : , 如果忘了就变成了 /part/name
// name 最终会被放到 $route.params 中作为 name 属性传递给目标组件
// 有些论坛会用年月日来作为路径参数查询不同时间的帖子 /2020/08/12/article
path: '/part/:name/:age',
component: Part
},
效果图:
总结:
- ?key=value 用
$route.query.key取值- /值 提前在路由规则/path/:key 用
$route.params.key取值
8. Vue路由 - 重定向
了解: redirect
匹配path后, 强制切换到目标path上
基本用法
- 网页打开url默认hash值是/路径
- redirect是设置要重定向到哪个路由路径 例如: 网页默认打开, 匹配路由"/", 强制切换到"/find"上
const routes = [
{
path: "/", // 默认hash值路径
redirect: "/find" // 重定向到/find
// 浏览器url中#后的路径被改变成/find-重新匹配数组规则
}
]
总结:
- 强制重定向后, 还会重新来数组里匹配一次规则
9. Vue路由 - 404页面
了解
如果路由hash值, 没有和数组里规则匹配
默认给一个 404 页面
基本用法
路由最后, path匹配*(任意路径) – 前面不匹配就命中最后这个, 显示对应组件页面
- 创建 NotFound 页面
<template>
<img src="../assets/404.png" alt="">
</template>
<script>
export default {
}
</script>
<style scoped>
img{
width: 100%;
}
</style>
- 在main.js - 修改路由配置
import NotFound from '@/views/NotFound'
const routes = [
// ...省略了其他配置
// 404在最后(规则是从前往后逐个比较path)
// 在配置规则的最底部写通配符 * 匹配所有路径
// 兜底的规则, 当用户访问的路径都不匹配时, 就跳到这条规则
{
path: "*",
component: NotFound
}
]
总结:
- 如果路由未命中任何规则, 给出一个兜底的404页面
10. Vue路由 - 模式设置
了解
修改路由在地址栏的模式
基本用法
hash路由例如: http://localhost:8080/#/home
history路由例如: http://localhost:8080/home (以后上线需要服务器端支持, 否则找的是文件夹)
11. Vue路由 - 编程式导航_跳转
了解
用JS代码来进行跳转
基本用法
this.$router.push({
path: "路由路径", // 都去 router/index.js定义
name: "路由名"
})
- main.js - 路由数组里, 给路由起名字
{
path: "/find",
name: "Find",
component: Find
},
{
path: "/my",
name: "My123",
component: My
},
path: "/part",
name: "Part",
component: Part
},
- App.vue - 换成span 配合js的编程式导航跳转
<template>
<div>
<div class="footer_wrap">
<span @click="btn('/find', 'Find')">发现音乐</span>
<span @click="btn('/my', 'My123')">我的音乐</span>
<span @click="btn('/part', 'Part')">朋友</span>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<script>
// 目标: 编程式导航 - js方式跳转路由
// 语法:
// this.$router.push({path: "路由路径"})
// this.$router.push({name: "路由名"})
// 注意:
// 虽然用name跳转, 但是url的hash值还是切换path路径值(例如: 点击我的音乐实现跳转时, url的hash值还是'/my')
// 场景:
// 方便修改: name路由名(在页面上看不见随便定义)
// path可以在url的hash值看到(尽量符合组内规范)
export default {
methods: {
btn(targetPath, targetName){
// 方式1: path跳转
this.$router.push({
// path: targetPath,
name: targetName
})
}
}
};
</script>
12. Vue路由 - 编程式导航_传参
了解
JS 跳转路由, 传参
基本用法
语法 query / params 任选 一个
this.$router.push({
path: "路由路径"
name: "路由名",
query: {
"参数名": 值
}
params: {
"参数名": 值
}
})
// 对应路由接收 $route.params.参数名 取值
// 对应路由接收 $route.query.参数名 取值
格外注意: 使用path会自动忽略params
App.vue
<template>
<div>
<div class="footer_wrap">
<span @click="btn('/find', 'Find')">发现音乐</span>
<span @click="btn('/my', 'My')">我的音乐</span>
<span @click="oneBtn">朋友-小梦</span>
<span @click="twoBtn">朋友-小牛</span>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<script>
// 目标: 编程式导航 - 跳转路由传参
// 方式1:
// params => $route.params.参数名
// 方式2:
// query => $route.query.参数名
// 重要: path会自动忽略params
// 推荐: name + query方式传参
// 注意: 如果当前url上"hash值和?参数"与你要跳转到的"hash值和?参数"一致, 爆出冗余导航的问题, 不会跳转路由
export default {
methods: {
btn(targetPath, targetName){
// 方式1: path跳转
this.$router.push({
// path: targetPath,
name: targetName
})
},
oneBtn(){
// 对象是无序属性的集合
this.$router.push({
path: '/part',
// name: 'Find', // 一般情况不建议两个都写, 如果都写了, name 生效
query: {
name: "小梦",
age: 18,
},
params: { // 使用 path 时会被自动忽略
username: '小梦牛'
}
})
},
twoBtn(){
this.$router.push({
name: 'Part',
query: {
name: '小牛'
}
})
}
}
};
</script>
13. Vue路由 - 组件缓存
了解
keep-alive -> 实现组件缓存效果
路由页面切换时, 都会执行哪些生命周期方法?
- 消失: destroyed销毁流程
- 出现: created初始化流程 - 所有代码重新执行
-
给3个子组件添加
created和destroyed方法 -
进行测试切换效果
* 路由切换时, 消失的页面, 会被销毁, 触发destroyed * 再切换回来, 重新创建, 所有代码重新执行, 效率不高 -
可以给
router-view外面包裹 Vue自带的keep-alive标签即可 -
组件缓存是把相关信息存储在内存中
-
再次测试, 会发现, 组件被缓存起来了, destroyed 方法销毁流程不会执行
14. Vue路由 - 组件缓存_匹配缓存
了解
include / exclude, 区别缓存组件
基本用法
需求: 只缓存<发现音乐>页面
- 需要先给要区分的组件们, 设置name字段和值
<script>
export default {
name: '组件名'
}
</script>
- 在keep-alive的时候, 使用 include / exclude 区分即可
-
include 包含哪些组件名需要缓存
<keep-alive include="Find"> <router-view></router-view> </keep-alive> -
exclude 不缓存哪些名字的组件
注意: exclude后面字符串逗号后面不能有空格
<keep-alive exclude="My,Part"> <router-view></router-view> </keep-alive>
-
15. Vue路由 - 组件缓存_钩子函数
了解
使用keep-alive后, 新增了2个钩子函数
基本用法
组件不执行销毁/初始化创建的方法了, 如何知道组件被失去激活/激活呢?
- activated --- 组件被激活状态
- deactivated --- 组件被失去激活状态
生命周期函数:在被keep-alive包含的组件/路由中,会多出两个生命周期的钩子:activated 与 deactivated。
- activated钩子:在在组件第一次渲染时会被调用,之后在每次缓存组件被激活时调用。
- Activated钩子调用时机:** 第一次进入缓存路由/组件,在mounted后面,beforeRouteEnter守卫传给 next 的回调函数之前调用,并且给因为组件被缓存了,再次进入缓存路由、组件时,不会触发这些钩子函数,beforeCreate created beforeMount mounted 都不会触发
- deactivated钩子:**组件被停用(离开路由)时调用。
- deactivated钩子调用时机**:使用keep-alive就不会调用beforeDestroy(组件销毁前钩子)和destroyed(组件销毁),因为组件没被销毁,被缓存起来了,这个钩子可以看作beforeDestroy的替代,如果你缓存了组件,要在组件销毁的的时候做一些事情,可以放在这个钩子里,组件内的离开当前路由钩子beforeRouteLeave => 路由前置守卫 beforeEach =>全局后置钩子afterEach => deactivated 离开缓存组件 => activated 进入缓存组件(如果你进入的也是缓存路由)
16. 路由导航守卫
“导航”表示路由正在发生改变。 官网 : 路由导航守卫
了解
router.beforeEach
router.afterEach
router.beforeResolve
beforeEnter
beforeRouteEnter
beforeRouteUpdate (2.2 新增)
beforeRouteLeave
基本用法
1. 全局前置守卫 - router.beforeEach
router.beforeEach 注册一个全局前置守卫:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// to:要去哪个页面
// from:从哪里来
// next:它是一个函数。
// 如果直接放行 next()
// 如果要跳到其它页 next(其它页)
})
示例代码:
router.beforeEach(async(to, from, next) => {
NProgress.start() // 开启进度条
const token = store.state.user.token
const userId = store.state.user.userInfo.userId
console.log(token, to.path, from.path)
if (token) {
if (to.path === '/login') { // 有 token还要去login
next('/')
NProgress.done() // 关闭进度条
} else { // 有 token,去其他页面,放行
if (!userId) {
await store.dispatch('user/getUserInfo')
}
next()
}
} else {
if (to.path === '/login') { // 没有token,去login,放行
next()
} else {
next('/login') // 没有token,去其他页面
NProgress.done()
}
}
})
小结
- router.beforeEach(回调(三个参数))
- 导航守卫函数中,一定要调用next( )
- to.path: to是一个路由对象,path表示路径,是它的一个属性
2. 全局后置钩子 - router.afterEach
router.afterEach 注册一个全局后置钩子:
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:
router.afterEach((to, from) => {
// ...
})
3. 全局解析守卫 - router.beforeResolve
在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
4. 路由独享的守卫
你可以在路由配置上直接定义 beforeEnter 守卫:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
5. 组件内的守卫
- beforeRouteEnter
- beforeRouteUpdate (2.2 新增)
- beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫 (2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
整理不易, 觉得写得还可以的, 多多点赞嘿嘿~