SPA 路由三部曲之核心原理

为了配合单页面 Web 应用快速发展的节奏,近几年,各种前端组件化技术栈层出不穷。经过不断的版本迭代 React、Vue 脱颖而出,成为当下最受欢迎的两大技术栈。 javascript

仅 7 个月的时间,两个技术栈的下载量就突破了百万,React 甚至突破了千万。无论是现下流行的 React、Vue,仍是红极一时的 Angular、Ember,只要是单页面 Web 应用,都离不开前端路由的配合。若是把单页面 Web 应用比做一间房,每一个页面对应房子中的各个房间,那么路由就是房间的门,无论房间装饰的有多漂亮,没有门,也没法展现在用户眼前,路由在单页面 Web 应用的地位也就不言而喻了。 css

为了能更详细的介绍前端路由,小编将从三个层面,由浅入深,一步一步的带领你们探索前端路由的实现原理。首先经过《SPA 路由三部曲之核心原理》了解前端路由的核心知识,紧接着《SPA 路由三部曲之 MyVueRouter 实践》将带领你们实现属于本身的 vue-router,最后《SPA 路由三部曲之 VueRouter 源码解析》将挑战自我,深度解析 vue-router 源码。《SPA 路由三部曲之核心原理》将从端路由的前世此生、核心原理解析、vue-router 与 react-router 应用对比三部分对前端路由进行初步了解。 html

前端路由前世此生

前端路由发展到今天,经历了后端路由、先后端路由过渡、前端路由的过程,若是你对前端路由的理解仍是懵懵懂懂,那有必要了解一下它的发展过程。前端

后端路由

路由这个概念最早是在后端出现的, Web 开发还在「刀耕火种」年代时,一直是后端路由占据主导地位,页面渲染彻底依赖服务器。 vue

在最开始的时候,HTML、CSS、JavaScript 的文件以及数据载体 json(xml) 等文件都是放到后端服务器目录下的,而且这些文件彼此是没有联系的,想要改变网站的布局,可能会改上百个 HTML,繁琐且毫无技术含量。后来聪明的工程师就将相同的 HTML 整理成模板,进行复用,成功减小了前端的工做量。前端工程师开始用模板语言代替手写 HTML,后端服务器目录的文件也变成了不一样的模板文件。java

这个时期,无论 Web 后端是什么语言的框架,都会有一个专门开辟出来的路由模块或者路由区域,用来匹配用户给出的 URL 地址,以及一些表单提交、页面请求地址。用户进行页面切换时,浏览器发送不一样的 URL 请求,服务器接收到浏览器的请求时,经过解析不一样的 URL 地址进行后端路由匹配,将模板拼接好后将之返回给前端完整的 HTML,浏览器拿到这个 HTML 文件后直接解析展现了,也就是所谓的服务端渲染。react

服务端渲染

服务端渲染页面,后端有完整的 HTML 页面,爬虫更容易获取信息,有利于 SEO 优化。对于客户端的资源占用更少,尤为是移动端,能够更省电。程序员

过渡

之后端路由为基础,开发的 Web 应用,都会存在一个弊端。每跳转到不一样的 URL,都是从新访问服务端,服务器拼接造成完整的 HTML,返回到浏览器,浏览器进行页面渲染。甚至浏览器的前进、后退键都会从新访问服务器,没有合理地利用缓存。 vue-router

随着前端页面复杂性愈来愈高,功能愈来愈完善,后端服务器目录下的代码文件会愈来愈多,耦合性也愈来愈严重。不只加大服务器的压力,也不利于良好的用户体验,代码维护。受限于以 JavaScript 为表明的前端技术还没有崛起,这个痛点成了程序员的最大难题。json

直到 1998 年,微软的 Outloook Web App 团队提出 Ajax 的基本概念(XMLHttpRequest 的前身),相信你们对这个技术已经很是熟悉了,浏览器实现异步加载的一种技术方案,并在 IE5 经过 ActiveX 来实现了这项技术。有了 Ajax 后,页面操做就不用每次都刷新页面,体验带来了极大的提高。

2005 年 Google Map 的发布让 Ajax 这项技术发扬光大,向人们展现了它真正的魅力,让其不只仅局限于简单的数据和页面交互,也为后来异步交互体验方式的繁荣发展奠基了基础。2008 年,Google V8 引擎发布,JavaScript 随之崛起,前端工程师开始借鉴后端模板思想,单页面应用就此诞生。2009 年,Google 发布 Angularjs 将 MVVM 及单页面应用发扬光大,由衷的佩服 Google 的强大。

单页应用不只在页面交互是无刷新的,连页面跳转都是无刷新的,为了配合实现单页面应用跳转,前端路由孕育而生。

前端路由

前端路由相较于后端路由的一个特色就是页面在不彻底刷新的状况下进行视图的切换。页面 URL 变了,可是并无从新加载,让用户体验更接近原生 app。

前端路由的兴起,使得页面渲染由服务器渲染变成了前端渲染。为何这么说呢!请求一个 URL 地址时,服务器不须要拼接模板,只需返回一个 HTML 便可,通常浏览器拿到的 HTML 是这样的:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Demo</title>
  <link href="app.css" rel="stylesheet"/>
</head>
<body>
  <div id="app"></div>
  <script type="text/javascript" src="app.js"></script>
</body>
</html>

这里空荡荡的只有一个 <div id="app"></div>,以及一系列的 js 文件,因此说这个 HTML 是不完整的。咱们看到的页面是经过这一系列的 js 渲染出来的,也就是前端渲染。前端渲染经过客户端的算力来解决页面的构建,很大程度上缓解了服务端的压力。

客户端渲染

单页面开发是趋势,但也不能拈轻怕重,忽略前端渲染的缺点。因为服务器没有保留完整的 HTML,经过 js 进行动态 DOM 拼接,须要耗费额外的时间,不如服务端渲染速度快,也不利于 SEO 优化。因此说,实际开发中,不能盲目选择渲染方式,必定要基于业务场景。对于没有复杂交互,SEO 要求严格的网站,服务器渲染也是正确的选择。

核心原理解析

路由描述了 URL 与 UI 之间的映射关系,这种映射是单向的,即 URL 变化引发 UI 更新(无需刷新页面)。前端路由最主要的展现方式有 2 种:

  • 带有 hash 的前端路由:地址栏 URL 中有 #,即 hash 值,很差看,但兼容性高。
  • 不带 hash 的前端路由:地址栏 URL 中没有 #,好看,但部分浏览器不支持,还须要后端服务器支持。

在 vue-router 和 react-router 中,这两种展现形式,被定义成两种模式,即 Hash 模式与 History 模式。前端路由实现原理很简单,本质上就是检测 URL 的变化,截获 URL 地址,经过解析、匹配路由规则实现 UI 更新。如今就跟着小编一块儿来揭开它神秘的面纱吧!

Hash

一个完整的 URL 包括:协议、域名、端口、虚拟目录、文件名、参数、锚。

URL 组成

hash 值指的是 URL 地址中的锚部分,也就是 # 后面的部分。hash 也称做锚点,是用来作页面定位的,与 hash 值对应的 DOM id 显示在可视区内。在 HTML5 的 history 新特性出现前,基本都是使用监听 hash 值来实现前端路由的。hash 值更新有如下几个特色:

  • hash 值是网页的标志位,HTTP 请求不包含锚部分,对后端无影响
  • 由于 HTTP 请求不包含锚部分,因此 hash 值改变时,不触发网页重载
  • 改变 hash 值会改变浏览器的历史记录
  • 改变 hash 值会触发 window.onhashchange() 事件

而改变 hash 值的方式有 3 种:

  • a 标签使锚点值变化,例: <a href='#/home'></a>
  • 经过设置 window.location.hash 的值
  • 浏览器前进键(history.forword())、后退键(history.back())

综上所述,这 3 种改变 hash 值的方式,并不会致使浏览器向服务器发送请求,浏览器不发出请求,也就不会刷新页面。hash 值改变,触发全局 window 对象上的 hashchange 事件。因此 hash 模式路由就是利用 hashchange 事件监听 URL 的变化,从而进行 DOM 操做来模拟页面跳转。
hash 流程图

History

在讲解 History 以前,你们先思考一个问题,点击浏览器左上角的回退按钮为何能回到以前的浏览记录,点击前进按钮就能回到回退以前的浏览记录?这是由于浏览器有一个相似栈的历史记录,遵循先进后出的规则。URL 的每次改变,包括 hash 值的变化都会在浏览器中造成一条历史记录。window 对象经过 history 对象提供对览器历史记录的访问能力。

  • history.length
    出于安全考虑,History 对象不容许未受权代码访问历史记录中其它页面的 URLs,但能够经过 history.length 访问历史记录对象的长度。
  • history.back()
    回退到上一个历史记录,同浏览器后退键
  • history.forward()
    前进到下一个历史记录,同浏览器前进键
  • history.go(n)
    跳转到相应的访问记录;若 n > 0,则前进,若 n < 0,则后退,若 n = 0,则刷新当前页面

为了配合单页面的发展,HTML5 对 History API 新增的两个方法:pushState()、replaceState(),均具备操纵浏览器历史记录的能力。

history.pushState(state, title, URL)

pushState 共接收 3 个参数:

  • state:用于存储该 URL 对应的状态对象,能够经过 history.state 获取
  • title:标题,目前浏览器并不支持
  • URL:定义新的历史 URL 记录,须要注意,新的 URL 必须与当前 URL 同源,不能跨域

pushState 函数会向浏览器的历史记录中添加一条,history.length 的值会 +1,当前浏览器的 URL 变成了新的 URL。须要注意的是:仅仅将浏览器的 URL 变成了新的 URL,页面不会加载、刷新。简单看个例子:

经过 history.pushState({ tag: "cart" }, "", "cart.html"),将 /home.html 变成 /cart.html 时,只有 URL 发生了改变,cart.html 页面并无加载,甚至浏览器都不会去检测该路径是否是存在。这也就是证实了,pushState 在不刷新页面的状况下修改浏览器 URL 连接,单页面路由的实现也就是利用了这一个特性。

细心地童鞋应该发现了,经过 pushState 设置新的 URL 的方法与经过 window.location='#cart' 设置 hash 值改变 URL 的方法有类似之处:URL 都发生了改变,在当前文档内都建立并激活了新的历史记录条目,但页面均没有从新渲染,浏览器没有发起请求。那前者的优点又是什么呢?

  • 新的 URL 能够是任意同源的 URL,而 window.location,只能经过改变 hash 值才能保证留在当前 document 中,浏览器不发起请求
  • 新的 URL 能够是当前 URL,不改变,就能够建立一条新的历史记录项,而 window.location 必须设置不一样的 hash 值,才能建立。假如当前URL为 /home.html#foo,使用 window.location 设置 hash 时,hash
    值必须不能是 #foo,才能建立新的历史记录
  • 能够经过 state 参数在新的历史记录项中添加任何数据,而经过 window.location 改变 hash 的方式,只能将相关的数据转成一个很短的字符串,以 query 的形式放到 hash 值后面
  • 虽然 title 参数如今还不能被全部的浏览器支持,前端发展这么快,谁能说的准以后发生的事情呢!

history.replaceState(state, title, URL)

replaceState 的使用与 pushState 很是类似,都是改变当前的 URL,页面不刷新。区别在于 replaceState 是修改了当前的历史记录项而不是新建一个,history.length 的值保持不变。

从上面的动画,咱们就能够知道,经过 history.replaceState({ tag: "cart" }, "", "cart.html") 改变 URL 以前,history 的历史记录为 /classify.html/home.html,URL 改变以后,点击浏览器后退键,直接回到了 /classify.html,跳过了 /home.html。也就证实了 replaceState 将历史记录中的 /home.html 修改成 /cart.html,而不是新建 /cart.html

window.onpopstate()

经过 a 标签或者 window.location 进行页面跳转时,都会触发 window.onload 事件,页面完成渲染。点击浏览器的后退键或前进键,根据浏览器的不一样机制,也会从新加载(Chrome 浏览器),或保留以前的页面(Safari 浏览器)。而对于经过 history.pushState() 或 history.replaceState() 改变的历史记录,点击浏览器的后退键或前进键页面是没有反应的,那该如何控制页面渲染呢?为了配合 history.pushState() 或 history.replaceState(),HTML5 还新增了一个事件,用于监听 URL 历史记录改变:window.onpopstate()。

官方对于 window.onpopstate() 事件的描述是这样的:

每当处于激活状态的历史记录条目发生变化时,popstate 事件就会在对应 window 对象上触发。 若是当前处于激活状态的历史记录条目是由 history.pushState() 方法建立,或者由 history.replaceState() 方法修改过的, 则 popstate 事件对象的 state 属性包含了这个历史记录条目的 state 对象的一个拷贝。调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。popstate 事件只会在浏览器某些行为下触发, 好比点击后退、前进按钮(或者在JavaScript 中调用 history.back()、history.forward()、history.go()方法),此外,a 标签的锚点也会触发该事件。

第一次读到这段话的时候似懂非懂,思考了好久,也作了不少的例子,发现其中的坑不少,这些坑主要是由于每一个浏览器机制不一样。官方文档对 window.onpopstate() 的描述不多,也有不少不明确的地方,根据本身的测试,来拆解一下官网描述,若是有不对的,还但愿你们指出。

1.每当处于激活状态的历史记录条目发生变化时,popstate 事件就会在对应 window 对象上触发。

对这句话的理解是,在浏览器中输入一个 URL ,使其处于激活状态,无论经过哪一种方式,只要 URL 改变,popstate 就会触发。但实际状况倒是:只有经过 pushState 或 replaceState 改变的 URL,在点击浏览器后退键的时候才会触发,若是是经过 a 标签或 window.location 实现 URL 改变(不是改变锚点)页面跳转,在点击浏览器回退键的时候,并不会触发。对这种状况,我有两个猜想:

  • popstate 事件是异步函数。因为经过 a 标签或 window.location 实现 URL 改变时,当前页面卸载,新的页面加载。因为 popstate 事件是异步的,在页面卸载以前并没来得及加载。
  • 只有触发新增的 pushState 与 replaceState 改变的历史记录条目,才会触发 popstate 事件,毕竟 popstate 事件的出现是为了配合 pushState 与 replaceState。

查阅了不少资料,这两个猜想没有获得证明,但有一点能够确定,想要监听到 popstate 事件,必须是使用 pushState 与 replaceState 改变的历史记录。

2.调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件,popstate 事件只会浏览器的某些行为下触发。

因为各个浏览器的机制不一样,测试结果也是不一样的。咱们先在 Chrome 浏览器下作个测试:
home.html

<div>
  <h3>home html</h3>
  <div id="btn" class="btn">跳转至 cart.html</div>
  <a href="classify.html"> a 标签跳转至 classify.html</a>
</div>
<script>
  document.getElementById('btn').addEventListener('click', function(){
       history.replaceState({ tag: "cart" }, "", "cart.html")
   }, false); 
   window.addEventListener('popstate', ()=>{
      console.log('popstate home 跳转')
   })
</script>

咱们进行这样的操做:当前 URL 为 /home.html,经过 history.pushState({ tag: "cart" }, "", "cart.html") 将当前 URL 变成了 /cart.html。这个过程当中,home.html 中的 popstate 事件确实没有触发。此时点击浏览器后退键,URL 变回了/home.htmlhome.html 中的 popstate 事件触发了。

那若是咱们跳出 /home.html 的 document 呢?经过 history.pushState({ tag: "cart" }, "", "cart.html") 将当前 URL 变成了 /cart.html 后,点击 a 标签将 URL 变为 /classify.html

执行到这里,咱们须要明确一点:a 标签改变 URL,浏览器会从新发起请求,页面发生了跳转,window 对象也发生了改变。popstate 官方文档第一句指出: popstate 事件是在对应 window 对象上触发。此时,咱们点击浏览器后退键,URL 变成 /cart.html,执行 /cart.html 中的 load 事件,页面加载。再次点击浏览器后退键,URL 变为 /home.html/cart.html 中的 popstate 事件触发,页面未渲染。

popstate 事件虽然触发了,可是是 cart.html 页面中定义的 popstate 事件,并非 home.html 的事件。而且一样的浏览器回退键操做,在 Safari 浏览器的展现是这样的:

在浏览器回退时,Safari 浏览器与 Chrome 浏览器对于页面的加载出现了差别。classify.html 回退到 cart.html ,URL 变成了 /cart.html,但触发了 home.html 中的 popstate 事件,继续回退,URL 变成了 /home.html, 依然触发了 home.html 中 popstate 事件。

Chrome 浏览器与 Safari 浏览器差别的产生与浏览器对 popstate 事件处理有关系。至于浏览器内部是怎样处理的,小编也没有研究清楚。虽然 Chrome 浏览器与 Safari 浏览器对于 popstate 事件的处理方式不同,可是 URL 的回退路径是一致的,彻底符合历史记录后进先出的规则。

在实际开发中,这种状况也是存在的:URL 由 /home.html/cart.html 的改变,就相似单页面开发中的跳转。若此时在 cart.html 中,须要使用 pushState 跳出单页面,进入登陆页,用户在登陆页点击浏览器回退,或移动端手势返回。上述状况就会出现,Chrome 浏览器与 Safari 浏览器渲染页面不一致。

popstate 官网描述是“popstate 事件会在对应 window 对象上触发”,注意是对应 window 对象,这个概念就比较模糊了,指的是触发 pushState 的 window 对象,仍是 pushState 新定义的 window 对象。根据咱们上述的测试,都有可能触发 popstate 事件。因此童鞋们,在遇到上面状况时,必定不要忘记在相关的两个页面中都要作 popstate 监听处理。

3.a 标签的锚点也能够触发 popstate 事件的方法

与 pushState 和 replaceState 不一样,a 标签锚点的变化会当即触发 popstate 事件。这里咱们扩展一下思路,a 标签作的事情就是改变了 hash 值,那经过 window.location 改变 hash 值是否是也是能当即触发 popstate。答案是确定的,也会当即触发 popstate。

经过 hash 小节的了解,hash 值的改变会触发 hashchange 事件,因此,hash 值的改变会同时触发 popstate 事件与 hashchange 事件,但若是改变的 hash 值与当前 hash 值同样的话,hashchange 事件不触发,popstate 事件触发。以前咱们说过,window.location 设置的 hash 值必须与当前 hash 值不同才能新建一条历史记录,而 pushState 却能够。

结合上述,在浏览器支持 pushState 的状况下,hash 模式路由也可使用 pushState 、replaceState 和 popstate 实现。pushstate 改变 hash 值,进行跳转,popstate 监听 hash 值的变化。小小的剧透,vue-router 中无论是 hash 模式,仍是 history 模式,只要浏览器支持 history 的新特性,使用的都是 history 的新特性进行跳转。

前端路由应用

其实 history 和 hash 都是浏览器自有的特性,单页面路由只是利用了这些特性。在不跳出当前 document 的状况下,除了 history 自身的兼容性以外,各个浏览器都不会存在差别,而单页面开发就是在一个 document 中完成全部的交互,这二者的完美结合,将前端开发提高到了一个新的高度。

vue-router 和 react-router 是如今最流行的路由状态管理工具。二者实现原理虽然是一致的,但因为所依赖的技术栈不一样,使用方式也略有不一样。在 react 技术栈开发时,大部分的童鞋仍是喜欢使用 react-router-dom ,它基于 react-router,加入了在浏览器运行环境下的一些功能。

注入方式

1. vue-router

vue-router 能够在 vue 项目中全局使用,vue.use() 功不可没。经过 vue.use(),向 VueRouter 对象注入了 Vue 实例,也就是根组件。根组件将 VueRouter 实例一层一层的向下传递,让每一个渲染的子组件拥有路由功能。

import VueRouter from 'vue-router'
const routes = [
    { path: '/',name: 'home',component: Home,meta:{title:'首页'} }
]
const router = new myRouter({
    mode:'history',
    routes
})
Vue.use(VueRouter)

2. react-router-dom

react-router 的注入方式是在组件树顶层放一个 Router 组件,而后在组件树中散落着不少 Route 组件,顶层的 Router 组件负责分析监听 URL 的变化,在其下面的 Route 组件渲染对应的组件。在完整的单页面项目中,使用 Router 组件将根组件包裹,就能完成保证正常的路由跳转。

import { BrowserRouter as Router, Route } from 'react-router-dom';
class App extends Component {
    render() {
        return (
            <Router>
                <Route path='/' exact component={ Home }></Route>
            </Router>
        )
    }
}

基础组件

1. vue-router 提供的组件主要有 <outer-link/> 和 <router-view/>

  • <router-link/> 能够操做 DOM 直接进行跳转,定义点击后导航到哪一个路径下;对应的组件内容渲染到 <router-view/> 中。

2. react-router-dom 经常使用到的是 <BrowserRouter/>、<HashRouter/>、<Route/>、<Link/>、<Switch/>

  • <BrowserRouter/>、<HashRouter/> 组件看名字就知道,用于区分路由模式,而且保证 React 项目具备页面跳转能力。
  • <Link /> 组件与 vue-router 中的 <router-link/> 组件相似,定义点击后的目标导航路径,对应的组件内容经过 <Route /> 进行渲染。
  • <Switch/> 用来将 react-router 由包容性路由转换为排他性路由,每次只要匹配成功就不会继续向下匹配。vue-router 属于排他性路由。

路由模式

1. vue-router 主要分为 hash 和 history 两种模式。在 new VueRouter() 时,经过配置路由选项 mode 实现。

  • Hash 模式:地址栏 URL 中有 #。vue-router 优先判断浏览器是否支持 pushState,若支持,则经过 pushState 改变 hash 值,进行目标路由匹配,渲染组件,popstate 监听浏览器操做,完成导航功能,若不支持,使用 location.hash 设置 hash 值,hashchange 监听 URL 变化完成路由导航。
  • History 模式:地址栏 URL 中没有 #。与 Hash 模式实现导航的思路是同样的。不一样的是,vue-router 提供了 fallback 配置,当浏览器不支持 history.pushState 控制路由是否应该回退到 hash 模式。默认值为 true。

    网上资料对 Hash 路由模式的原理分析大都是经过 location.hash 结合 hashchange 实现,与上述描述的 hash 路由模式的实现方式不一样,这也是小编最近阅读 vue-router 源码发现的,鼓励小伙伴们读一下,确定会收获满满!

2. react-router-dom 经常使用的 2 种模式是 browserHistory、hashHistory,直接用 <BrowserRouter> 或 <HashHistory> 将根组件(一般是 <App> )包裹起来就能实现。

  • react-router 的实现依赖 history.js,history.js 是 JavaScript 库。<BrowserRouter> 、 <HashHistory> 分别基于 history.js 的 BrowserHistory 类、HashHistory 类实现。
  • BrowserHistory 类经过 pushState、replaceState 和 popstate 实现,但并无相似 vue-router 的兼容处理。HashHistory 类则是直接经过 location.hash、location.replace 和 hashchange 实现,没有优先使用 history 新特性的处理。

嵌套路由与子路由

1. vue-router 嵌套路由

在 new VueRouter() 配置路由表时,经过定义 Children 实现嵌套路由,不管第几层的路由组件,都会被渲染到父组件 <router-view/> 标识的地方。

router.js

const router = new Router({
    mode:'history',
    routes: [{
        path: '/nest',
        name: 'nest',
        component: Nest,
        children:[{
            path:'first',
            name:'first',
            component:NestFirst
        }]
    }]
})

nest.vue

<div class="nest">
    一级路由 <router-view></router-view>
</div>

first.vue

<div class="nest">
    二级路由 <router-view></router-view>
</div>

/nest 下设置了二级路由 /first,二级对应的组件渲染在一级路由匹配的组件 <router-view/> 标识的地方。在配置子路由时,path 只须要是当前路径便可。

2. react-router 子路由

react-router 根组件会被渲染到 <Router/> 指定的位置,子路由则会做为子组件,由父组件指定该对象的渲染位置。若是想要实现上述 vue-router 嵌套的效果,须要这样设置:

route.js

const Route = () => (
    <HashRouter>
        <Switch>
            <Route path="/nest" component={Nest}/>
        </Switch>
    </HashRouter>
);

nest.js

export default class Nest extends Component {
    render() {
        return (
            <div className="nest">
                一级路由
                <Switch>
                    <Route path="/nest/first" component={NestFirst}/>
                </Switch>
            </div>
        )
    }
}

first.js

export default class NestFirst extends Component {
    render() {
        return (
            <div className="nest">
                二级路由
                <Switch>
                    <Route exact path="/nest/first/second" component={NestSecond}/>
                </Switch>
            </div>
        )
    }
}

其中,/nest 为一级路由,/fitst 二级路由匹配的组件,做为一级路由的子组件。react-router 定义子路由 path 时,须要写完整的路径,即父路由的路径要完整。

路由守卫

1. vue-router 导航守卫分为全局守卫、路由独享守卫、组件内的守卫三种。主要用来经过跳转或取消的方式守卫导航。

a. 全局守卫

  • beforeEach — 全局前置钩子(每一个路由调用前都会触发,根据 from 和 to 来判断是哪一个路由触发)
  • beforeResolve — 全局解析钩子(和 router.beforeEach 相似,区别是在导航被确认以前,同时在全部组件内守卫和异步路由组件被解析以后,解析守卫就被调用)
  • afterEach — 全局后置钩子

b. 路由独享守卫

  • 路由配置上能够直接定义 beforeEnter 守卫。

c. 组件内守卫

  • beforeRouteEnter — 在渲染该组件的对应路由被 confirm 前调用,不能获取组件实例 this,由于当守卫执行前,组件实例还没被建立。
  • beforeRouteUpdate — 当前路由改变,可是该组件被复用时调用
  • beforeRouteLeave — 导航离开该组件的对应路由时调用

2. react-router 4.0 版本以前,提供了 onEnter 和 onLeave 钩子,实现相似 vue-router 导航守卫的功能,但 4.0 版本后取消了该方法。

路由信息

1. vue-router 中 $router、$route 对象

vue-router 在注册时,为每一个 vue 实例注入了 $router、$route 对象。$router 为 router 实例信息,利用 push 和 replace 方法实现路由跳转,$route 提供当前激活的路由信息。

import router from './router'
export default new Vue({
    el: '#app',
    router,
    render: h => h(App),
})

2. react-router 中 history、location 对象

在每一个由 <Route/> 包裹的组件中提供了 history、location 对象。利用 this.props.history 的 push、replace 方法实现路由导航,this.props.location 获取当前激活的路由信息。

const BasicRoute = () => (
    <div>
        <HeaderNav></HeaderNav>
        <HashRouter>
            <Switch>
                <Route exact path="/" component={Home}/>
            </Switch>
        </HashRouter>
    </div>
);

若是想要得到 history、location 必定是 <Route /> 包裹的组件。因此在 <HeaderNav/> 中是没法获取这两个对象的,而 <Home/> 组件是能够的。

vue-router 是全局配置方式,react-router 是全局组件方式,但二者呈现给开发者的功能其实是大同小异的。固然,vue-router 与 react-router 在使用上的差别不只仅是小编说的这些。说到底,无论用什么样的方式实现,前端路由的实现原理都是不会变的。

总结

前端路由的初步体验立刻就要结束了,在决定深刻研究前端路由以前,小编自信满满,感受应该不会花费很大的精力与时间,可事实是,涉及到的知识盲区愈来愈多,信心在逐渐瓦解。好在结局不错,收获了不少,也但愿《SPA 路由三部曲之核心原理》这篇文章能让你们有所收获,哪怕只是一个知识点。

小编已经在争分夺秒的准备《SPA 路由三部曲之 MyVueRouter 实践》、《SPA 路由三部曲之 VueRouter 源码解析》过程当中了,小编相信是不会让你失望的,请充满期待吧!

PS:文章中有些是我的观点,若是不对,欢迎交流、指正!

相关文章
相关标签/搜索