文章目录
- 一、SPA与前端路由
- 二、vue-router实现原理(模式)
- hash模式
- history模式
- 三、vue-router中的route和router
- 四、vue-router有哪几种导航守卫
- 全局守卫
- 路由独享的守卫
- 路由组件内的守卫
- vue-router完整的导航解析流程
一、SPA与前端路由
前端路由本质是,通过改变 URL,在不重新请求页面的情况下,更新页面视图。
传统的页面应用,是用一些超链接来实现页面切换和跳转的。而vue-router单页面应用中,则是路径之间的切换,也就是组件的切换。路由模块的本质 就是建立起url和页面之间的映射关系。
- SPA(单页面应用,全程为:Single-page Web applications)指的是只有一张Web页面的应用,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序,简单通俗点就是在一个项目中只有一个html页面,它在第一次加载页面时,将唯一完成的html页面按需加载的组件一起下载下来,所有的组件的展示与切换都在这唯一的页面中完成,这样切换页面时,不会重新加载整个页面,而是通过路由来实现不同组件之间的切换。
优点
- 具有桌面应用的即时性、网站的可移植性和可访问性
- 用户体验好、快,内容的改变不需要重新加载整个页面
- 良好的前后端分离,分工更明确
缺点
- 不利于搜索引擎的抓取
- 首次渲染速度相对较慢
二、vue-router实现原理(模式)
更新视图但不重新请求页面,是前端路由原理的核心之一,目前在浏览器环境中这一功能的实现主要有2种方式:
- hash – 默认值,利用 URL 中的hash(http://localhost:8080/#/login)
- history – 利用URL中的路径(http://localhost:8080/login)
hash模式和history模式的区别
- hash的url有’#'号,history没有
- history和hash都是利用浏览器的两种特性实现前端路由,history是利用浏览历史记录栈的API实现,hash是监听location对象hash值变化事件来实现
- history是H5新增,并且需要后端配合,如果后端不配合刷新新页面会出现404,hash不需要
- HashRouter的原理:通过window.onhashchange方法获取新URL中hash值,再做进一步处理;HistoryRouter的原理:通过history.pushState 使用它做页面跳转不会触发页面刷新,使用window.onpopstate 监听浏览器的前进和后退,再做其他处理
设置路由模式
const router=new VueRouter({
mode:'hash',
routes:[...]
})
hash模式
vue-router默认hash模式,使用URL的hash来模拟一个完成URL,于是当URL改变时,页面不会重新加载
- hash(#)是URL的锚点,代表的是页面中的某个位置,单单改的是#后的部分,浏览器只会滚动搭到相应的位置,不会重新加载页面,也就是说hash 出现在 URL 中,但不会被包含在 http 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面
- 同时每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置;
- 所以说Hash模式通过锚点值的改变,根据不同的值,渲染 指定DOM位置 的不同数据。
- hash 模式的原理是 onhashchange 事件(监测hash值变化),可以在 window 对象上监听这个事件。
通过下面的demo对hash操控路由并添加到历史记录有个更深刻的了解
有文件index.html、home.html、about.html,三个文件同目录
home.html、about.html随便放点东西,相当于vue里面两个子组件
index.html文件内容如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../jquery.min.js"></script>
<style>
.active{
color: red;
}
</style>
</head>
<body>
<ul id="navagation">
<li>
<a href="#home.html">home</a>
</li>
<li>
<a href="#about.html">about</a>
</li>
<li>
<button id="btn">后退</button>
<button id="btn2">前进</button>
</li>
</ul>
<div id="content"></div>
<script>
$(document).ready(function(){
if(location.hash){
var hashPage = location.hash.split('#')[1]
$('#content').load(hashPage)
$("#navagation a").removeClass('active')
$("#navagation a").each(function(){
if($(this).attr('href')===`#${hashPage}`){
$(this).addClass('active')
}
})
}
document.getElementById('btn').onclick = function(){
// window.history.go(-1)
window.history.back()
}
document.getElementById('btn2').onclick = function(){
window.history.forward()
}
window.onhashchange = function(e){
console.log(history.length)
// var hashPage = location.hash.split('#')[1]
var hashPage = e.newURL.split('#')[1]
$('#content').load(hashPage)
}
})
</script>
</body>
</html>
history模式
HTML5
history新增了两个API: history.pushState 和 history.replaceState
两个api都接受三个参数
- 状态对象(state object):一个JavaScript对象,与用pushState()方法创建的新历史记录条目关联。无论何时用户导航到新创建的状态,popstate事件都会被触发,并且事件对象的state属性都包含历史记录条目的状态对象的拷贝。
- 标题(title):FireFox浏览器目前会忽略该参数,虽然以后可能会用上。考虑到未来可能会对该方法进行修改,传一个空字符串会比较安全。或者,你也可以传入一个简短的标题,标明将要进入的状态。
- 地址(URL): 新的历史记录条目的地址。浏览器不会在调用pushState()方法后加载该地址,但之后,可能会试图加载,例如用户重启浏览器。新的URL不一定是绝对路径;如果是相对路径,它将以当前URL为基准;传入的URL与当前URL应该是同源的,否则,pushState()会抛出异常。该参数是可选的;不指定的话则为文档当前URL。
相同之处是两个API都会操作浏览器的历史记录,而不会引起页面的刷新。不同之处在于pushState会增加一条新的历史记录,而replaceState则会替换当前的历史记录
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<style>
.active{
color: red;
}
</style>
</head>
<body>
<ul id="navagation">
<li>
<button id="btn1">pushState</button>
<button id="btn2">getLen</button>
<button id="btn3">replaceState</button>
<button id="btn4">前进</button>
<button id="btn5">后退</button>
</li>
</ul>
<div>栈:<span></span></div>
<div id="content"></div>
<script>
$(document).ready(function(){
let x = 1;
$('span').html("当前历史记录栈总数:"+ history.length)
let page = ''
let y = 1;
$('#btn1').click(function(){
y = x
// 压栈
page = "test"+x+'.html'
history.pushState({page:page},"",page)
x++
$('#content').load(page)
})
$("#btn2").click(function(){
$('span').html("当前历史记录栈总数:"+ history.length)
})
$('#btn3').click(function(){
if(y<=1) return
y--
// 替换
history.replaceState({},"","test"+y+'.html')
x = y+1
})
$('#btn4').click(function(){
// 前进
history.forward()
})
$('#btn5').click(function(){
// 后退
history.back()
})
window.addEventListener("popstate",(e)=>{
console.log(e)
if(!e.state) return
$("#content").load(e.state.page)
})
})
</script>
</body>
</html>
三、vue-router中的route和router
router 是VueRouter的实例,router是一个全局的路由对象,里面有很多的属性和方法,常见的就是this.$router.push()
route相当于正在跳转的路由对象,可以从route里面获取hash,name ,path,query,params等属性方法
四、vue-router有哪几种导航守卫
有三种:全局守卫,路由独享守卫,路由组件内的守卫
主要用来通过跳转或取消的方式守卫导航。
例如判断登录信息:没登录全部跳到登录页。判断必要操作是否进行没进行的话中断跳转。
全局守卫
- router.beforeEach 全局前置守卫 进入路由之前,必须调用next
应用场景:导航前置守卫 在路由跳转之前进行判断和拦截,一般用来做一些进入页面的限制,比如未登录不能进入某些页面
有三个参数- to: Route: 即将要进入的目标 路由对象
- from: Route: 当前导航正要离开的路由
- next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
router.beforeEach((to, from,next) => {
const user = sessonStorage.getItem('infromation')
if(to.path=='/login'){
next()
}else if(user){
next()
}else{
next('/login')
}
})
- router.beforeResolve 全局解析守卫 在beforeRouteEnter调用之后调用,必须调用next
router.beforeResolve((to, from,next) => {
next()
})
- router.afterEach 全局后置钩子 进入路由之后
应用场景:跳转路由后组件默认在顶部
router.afterEach((to, from) => {
document.title = to.title
window.scrollTo(0,0)
})
为路由跳转加进度条
下载nprogress包 npm i nprogress
在main.js中引入
import NProgress from 'nprogress' // 导入进度条插件
import 'nprogress/nprogress.css' // 导入样式
// 默认关闭进度条
NProgress.configure({ showSpinner: false })
在全局导航守卫中添加
router.beforeEach((to, from, next) => {
// 在准备跳转之前开启进度条
NProgress.start()
// 判断是否登录
next()
})
// 后置守卫
router.afterEach(() => {
// finish progress bar
NProgress.done()
})
路由独享的守卫
这个路由用的比较少,必须调用next
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
next()
}
}
]
})
路由组件内的守卫
组件内的守卫有3种,beforeRouteEnter(第一次创建组件实例前)、beforeRouteUpdate(在组件被复用的时候调用)、beforeRouteLeave(离开组件)
beforeRouteEnter:如/path/:id这一个路由,第一次导航/path/id001会进入该组件内守卫,切换导航为/path/id002的时候不会触发该守卫方法
beforeRouteUpdate: 如/path/:id这一个路由,第一次导航/path/id001不会触发该守卫卫,切换导航为/path/id002的时候会触发该守卫方法
beforeRouteLeav:当离开该导航才会触发该守卫方法
beforeRouteEnter(to,from,next){
console.log("beforeRouteEnter")
next(vm=> {
console.log(vm,'vm')
})
},
beforeRouteUpdate(to,from,next){
console.log("beforeRouteUpdate")
next()
},
beforeRouteLeave(to,from,next){
console.log("beforeRouteLeave")
next()
}
vue-router完整的导航解析流程
1.导航被触发。
2.在失活的组件里调用离开守卫。
3.调用全局的 beforeEach
守卫。
4.在重用的组件里调用 beforeRouteUpdate
守卫(2.2+)。
5.在路由配置里调用 beforeEnter
。
6.解析异步路由组件。
7.在被激活的组件里面调用 beforeRouterEnter
。
8.调用全局的 beforeResolve
守卫(2.5+)。
9.导航被确认。
10.调用全局的 afterEach
钩子。
11.触发 DOM 更新。
12.用创建好的实例调用 beforeRouteEnter
守卫中传给 next 的回调函数。
导航守卫执行顺序流程图