vue-router的实现原理hash/history、导航守卫、导航解析流程

一、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有哪几种导航守卫

有三种:全局守卫,路由独享守卫,路由组件内的守卫
主要用来通过跳转或取消的方式守卫导航。
例如判断登录信息:没登录全部跳到登录页。判断必要操作是否进行没进行的话中断跳转。

全局守卫

  1. 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')
    }
})

  1. router.beforeResolve 全局解析守卫 在beforeRouteEnter调用之后调用,必须调用next
router.beforeResolve((to, from,next) => {
	next()
})
  1. 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 的回调函数。

导航守卫执行顺序流程图

在这里插入图片描述

注意:只有 如/path/:id这一个路由,第一次导航/path/id001,切换导航为/path/id002的时候会触发beforeRouteUpdate守卫,其他时候都不会触发,缓存的组件再次进来的时候也不会触发;每次都会触发全局导航守卫;子组件不会触发导航守卫。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值