最开始我们就将到了,锚链接的特点是:会在地址栏上产生浏览历史。所以对于的<router-link>
也会有同样的效果。
但是,vue-router 为<router-link>
封装了一些 props 属性,以增强它的灵活性。
# 历史记录
- 默认
会调用 $router.push(),并且产生历史记录
- replace
会调用 $router.replace(),不会产生历史记录
应用思考
当有多级路由嵌套时,直接使用可以会造成历史回退困难,此时我们可以直接对一级路由进行 replace 处理。
<!-- replace属性:防止产生历史记录 -->
<router-link to="/home" replace>Home</router-link>
<router-link to="/about" replace>About</router-link>
# 一、路由导航
上面我们使用的 replace 属性,本质上讲就是帮我们调用的$router.replace()。
# 1、编程式导航
通俗的将就是没有直接通过点击<router-link>
进行页面跳转的,就是编程式(API 式)的。
如上面的:
$router.push()
、$router.replace()
提示
对应原生 js 中的:
window.location.href = 'new_url'
或window.location.replace('new_url')
那使用编程式的用什么好处呢?
答案 🍗
- 可以让链接跳转的功能不仅仅局限
<a>
标签 - 可以通过 js 自定义页面(路由)跳转的时机、方式等
以前:声明式写法
<ul class="nav">
<li v-for="item in titleList">
<router-link
:to="
{
name: 'part1',
params: {
title: item.title
}
}"
>
part1
</router-link>
</li>
</ul>
现在:编程式导航
<ul class="nav">
<li v-for="item in titleList">
<!-- 可以使用button标签了、触发时机可以自定义了 -->
<button @click="pushLink(item)">part1</button>
</li>
</ul>
export default {
methods: {
pushLink(item) {
this.$router.push({
name: item.name,
params: {
title: item.title
}
// path: item.path,
// query: {
// title: item.title
// }
})
}
}
}
# 2、API 使用
① 页面(路由)跳转
除了前面已经提到的导航 API 外,
router.push(location, onComplete?, onAbort?)
router.push(location).then(onComplete).catch(onAbort)
router.replace(location, onComplete?, onAbort?)
router.replace(location).then(onComplete).catch(onAbort)
② 历史回滚
还有:
$router.go(n)
用于快速的历史前进、后退(通常用得少)。$router.back()
用于后退一步$router.forward()
用于前进一步
提示
对应原生 js 中的:
window.history.back()
、window.history.forward()
和window.history.go(n)
<button @click="$router.back()">后退</button>
<button @click="$router.forward()">前进</button>
# 3、导航缓存
对于路由导航,有没有必要像动态路由/嵌套路由的keep-alive
一样,搞一下路径缓存之类的操作,用于提升用户的体验。
之前没有想过,但在接触了 vue-element-admin (opens new window) 后台模板项目后,我感觉是有必要的。
用户退出后,重新进入后台管理时,直接跳到上次退出前的使用页面。
代码实现
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !auth.isAuthenticated()) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
})
# 二、导航守卫
官方地址:
https://v3.router.vuejs.org/zh/guide/advanced/navigation-guards.html
# 1、基础认知
- 变化角度
-参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route
对象来应对这些变化,或使用 beforeRouteUpdate
的组件内守卫。
<script>
export default {
// 监听$route对象
watch: {
$route: function () {
//
}
},
// 在当前路由改变,但是该组件被复用时调用
beforeRouteUpdate(to, from, next) {
// 例如:对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候
}
}
</script>
- 作用角度
vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航
- 触发角度
初始化的时候、发生路由切换的时候
# 2、beforeEach()
全局前置守卫
基础结构:
router.beforeEach((to, from, next) => {
// to表示将要进入的目标 路由对象
console.log(to)
// from表示当前导航正要离开的 路由对象
console.log(from)
// 触发 resolve 钩子(表示放行)
next()
})
next 回调:
如下图所示,next 函数的 3 种调用方式最终会导致不同的结果:
![](https://blog-cloudbed.oss-cn-chengdu.aliyuncs.com/note-taking/20220608190639.png)
通常情况下,路由对象 to 和 from 的使用如下:to.path、to.name、to.meta.requiresAuth 等
// 设置全局前置守卫
router.beforeEach((to, from, next) => {
// 1、简化版
const token = localStorage.getItem('token')
if (to.path.startWith('/main') && !token) {
next('login')
}
// 2、详细版
// 是否进入后台
// if (to.path.startWith('/main')) {
// const token = localStorage.getItem('token')
// // 身份是否合法
// if (token) {
// next()
// } else {
// next('/login') // 当前的导航被中断,然后进入一个新的导航
// }
// } else {
// next(false) // URL 地址会重置到 from 路由对应的地址
// }
})
export default router
开发应用:后台 page 路径不规则时 ✍
- router/index.js(从 路径 角度)
可以采用白名单,也可以采用
to.meta.requiresAuth
// 白名单页面
const whiteList = ['/login', '/reg']
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
router.beforeEach((to, from, next) => {
if (whiteList.includes(to.path)) {
next()
} else {
let token = localStorage.getItem('Authorization')
if (token === null || token === '') {
next('/login')
} else {
next()
}
}
})
或者:
- router/index.js(从 token 角度)
不推荐,因为不但嵌套的两层 if、并且白名单页面还可以重复做了两次判断
// 无需验证token的页面
// 【因为登录、注册页面也没有token,根据全局前置守卫的定义不做白名单处理,会一直处于跳转——触发——跳转循环中】
const whiteList = ['/login', '/reg']
router.beforeEach((to, from, next) => {
// 和前面获取token的方式相比,其实vuex🚩默认也是从Application中取的
const token = $store.state.token
// 如果有token, 证明已登录
if (token) {
next()
} else {
// 白名单✨页面放行
if (whiteList.includes(to.path)) {
next()
} else {
// 如果其他页面请强制拦截并跳转到登录页面
next('/login')
}
}
next()
})
# 3、afterEach()
全局后置钩子
前置守卫,通常用于发生路由跳转前进行权限验证。但有时候,我们也需要一些操作必须在发生跳转后执行。
例如:document.title (确保真正的跳转完成了才设置)
router.afterEach((to, from) => {
document.title = to.meta.title || 'lencamo'
})
# 4、守卫分类
- 路由独享守卫
beforeEnter
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
- 组件内的守卫
beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
<script>
export default {
// 通常用来禁止用户在还未保存修改前突然离开
beforeRouteLeave(to, from, next) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}
}
</script>
# 5、导航解析流程
阶段 | 事件描述 |
---|---|
导航被触发 | |
【beforeRouteLeave ✨ 守卫】 | 在失活的组件里调用 |
~~~~~~~~~~~~~~~~~~~~ | ~~~~~~~~~~~~~~~~~~~~ |
解析异步路由组件 | |
beforeEach 🎈 | 全局调用 |
【beforeRouteUpdate 守卫(2.2+) 】 | 在重用的组件里调用 |
beforeEnter | 在路由配置里调用 |
解析异步路由组件 | |
~~~~~~~~~~~~~~~~~~~~ | ~~~~~~~~~~~~~~~~~~~~ |
【beforeRouteEnter 守卫】 | 在被激活的组件里调用 |
beforeResolve(2.5+) | 全局调用 |
导航被确认 | |
afterEach ✨ | 全局调用 |
触发 DOM 更新 |
# 三、router 构造 ✨
# 1、router 实例属性
export default new VueRouter({
mode: 'history', // 默认hash模式
// 根据下面内容自个体会😂
// history模式时:ip:端口/oj/home
// hash模式时:ip:端口/oj/#/home
base: '/oj'
// 打包之后的文件项目是放在服务器根目录下的 oj文件夹下的,而不是直接放在根目录
})
# 2、history 模式解读
区别:
指标 | hash | history |
---|---|---|
url 显示 | 有#,不美观 | 无#,很 nb |
支持版本 | 支持低版本浏览器 | HTML5 新特性 API |
请求地址 | hash 前面的部分 | 地址栏中的整个地址 |
回车刷新(发起页面请求) | 加载对应页面 | 可能 404 页面 |
history 的 404 页面 问题:
前端打包后的 dist 包中,只有 index.html。
所以,部署上线后,history 模式下发送的非根路径的页面请求,服务端是找不到的。
关于 404 Not found 前端预处理
- index.js
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/404',
component: () => import('@/views/error-page/404'),
hidden: true
},
// 含有通配符的路由应该放在最后 !!!
{ path: '*', component: 'NotFound' }
// 当使用一个通配符时,$route.params 内会自动添加一个名为 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分
// { path: '/:pathMatch(.*)', component: 'NotFound' }
// { path: '/:pathMatch(.*)*', component: 'NotFound' }
]
})
- NotFound.vue
<template>
<div>
<h2>NotFound:你当前的路由地址{{ $route.params.pathMatch }}匹配失败,请输入正确的路由地址!</h2>
</div>
</template>
# 3、部署问题
上面已经提到了 history 模式下,部署上线可能会存在的潜在问题。
前端临时解决:(开发环境)
module.exports = {
publicPath: '/',
devServer: {
// 解决history模式下,服务器请求不到资源返回404问题
// historyApiFallback: true
historyApiFallback: {
// index: '/oj/index.html' // 加不加.与publicPath一致
rewrites: [
{
from: /.*/g,
to: '/oj/index.html'
}
]
},
proxy: {}
}
}
后端根本解决:(生产环境 (opens new window))
express 部署
可以在本地部署一个简单的路由切换案例试一试 😂
var express = require('express')
var app = express()
app.use(history())
nginx 部署
# /conf/nginx.conf
user root
http{
# 项目一
server {
listen 80;
server_name localhost;
root /www/wwwroot/
# 代理/oj,而不是/是因为前端使用了base: '/oj',
location /oj {
root /www/wwwroot/oj;
index index.html index.htm;
# 配置url🎈重写语句(先返回404,然后返回index.html)
try_files $uri $uri/ /oj/index.html;
}
}
}
分析:
浏览器请求 http://localhost/oj/problem, nginx 发现没有资源,所以返回 404
nginx 紧接着又将 index.html 返回,浏览器加载后,又去加载其中引用的 js、css 等文件,然后路由规则根据地址栏中的地址,去匹配对应的组件进行渲染