-
Notifications
You must be signed in to change notification settings - Fork 385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
单页面应用路由实现原理:以 React-Router 为例 #109
Comments
“在第一次进入页面的时候,也会触发一次 onhashchange 事件,这保证了一开始的 hash 就能被识别。” 这句应该是错误的,页面在第一次加载时并不会触发hashchang事件,只有改变了hash值后才会触发。 |
@helloyxw 此处确实没有表述清楚,多谢指正。 |
最近在用 现在我是把 |
“三个 comppnent 一直都在...” |
赞 |
window.addEventListener('popstate', () => { 这个监听是为了做什么?原来以为是为了监听pushState,但MDN上提到:Note that just calling history.pushState() or history.replaceState() won't trigger a popstate event. 所以有点不理解这个监听是为了哪种场景? @youngwind |
这就是为了监听,手动点击浏览器的回退和前进按钮 这两种情况的,MDN说明的这个,结合题主说的,我的理解是:popState 能触发回调,你在回调里面处理路由变化的逻辑;但是你调用 history.pushState 和 replaceState,是不会触发 popState的,不会形成环路 |
赞 |
前言
2 年前我刚接触 react-router,觉得这玩意儿很神奇,只定义几个 Route 和 Link,就可以控制整个 React 应用的路由。不过那时候只想着怎么用它,也写过 2 篇与之相关的文章 #17 #73 (现在看来,那时候的文章写得实在是太差了)今天,我们来认真研究一番,希望能解决以下 3 个问题。
hash 的历史
最开始的网页是多页面的,后来出现了 Ajax 之后,才慢慢有了 SPA。然而,那时候的 SPA 有两个弊端:
怎么办呢? → 使用 hash
url 上的 hash 本意是用来作锚点的,方便用户在一个很长的文档里进行上下的导航,用来做 SPA 的路由控制并非它的本意。然而,hash 满足这么一种特性:改变 url 的同时,不刷新页面,再加上浏览器也提供 onhashchange 这样的事件监听,因此,hash 能用来做路由控制。(这部分红宝书 P394 也有相关的说明)后来,这种模式大行其道,onhashchange 也就被写进了 HTML5 规范当中去了。
下面举个例子,演示“通过改变 hash 值,对页面进行局部刷新”,此例子出自前端路由实现与 react-router 源码分析, By joeyguo
运行的效果如下图所示:

由图中我们可以看到:的确可以通过 hash 的改变来对页面进行局部刷新。尤其需要注意的是:在第一次进入页面的时候,如果 url 上已经带有 hash,那么也会触发一次 onhashchange 事件,这保证了一开始的 hash 就能被识别。
问题:虽然 hash 解决了 SPA 路由控制的问题,但是它又引入了新的问题 → url 上会有一个 # 号,很不美观
解决方案:抛弃 hash,使用 history
history 的演进
很早以前,浏览器便实现了 history。然而,早期的 history 只能用于多页面进行跳转,比如:
在 HTML5 规范中,history 新增了以下几个 API
通过
history.pushState
或者history.replaceState
,也能做到:改变 url 的同时,不会刷新页面。所以 history 也具备实现路由控制的潜力。然而,还缺一点:hash 的改变会触发 onhashchange 事件,history 的改变会触发什么事件呢? → 很遗憾,没有。怎么办呢?→ 虽然我们无法监听到 history 的改变事件,然而,如果我们能罗列出所有可能改变 history 的途径,然后在这些途径一一进行拦截,不也一样相当于监听了 history 的改变吗?
对于一个应用而言,url 的改变只能由以下 3 种途径引起:
第 2 和第 3 种途径可以看成是一种,因为 a 标签的默认事件可以被禁止,进而调用 JS 方法。关键是第 1 种,HTML5 规范中新增了一个 onpopstate 事件,通过它便可以监听到前进或者后退按钮的点击。
要特别注意的是:调用
history.pushState
和history.replaceState
并不会触发 onpopstate 事件。总结:经过上面的分析,history 是可以用来进行路由控制的,只不过需要从 3 方面进行着手。
React-Router v4
React-Router 的版本也是诡异,从 2 到 3 再到 4,每次的 API 变化都可谓翻天覆地,这次我们便以最新的 v4 进行举例。
运行的实际结果如下图所示:

由图中我们可以看出:所谓的局部刷新,其本质是:三个 comppnent 一直都在。当路由发生变化时,跟当前 url 匹配的 component 正常渲染;跟当前 url 不匹配的 component 渲染为 null,仅此而已,这其实跟 jQuery 时代的 show 和 hide 是一样的道理。现象我们已经观察到了,下面讨论实现思路。
思路分析
代码实现
本文的思路分析和代码实现,参考了这篇文章:build-your-own-react-router-v4, By Tyler;也可以对照着看译文版本:由浅入深地教你开发自己的 React Router v4, By 胡子大哈。相对于参考文章而言,我主要做了以下两处改动:
jsHistory.pushState
方法就可以在 JS 中控制路由导航。实现的效果如下图所示:

参考资料
本文涉及到代码可以参考这个仓库。
---------- 完 -------------
The text was updated successfully, but these errors were encountered: