在学习React Router时,看到有关History有如下描述:
React Router 是建立在 history 之上的。 简而言之,一个 history 知道如何去监听浏览器地址栏的变化, 并解析这个 URL 转化为 location 对象, 然后 router 使用它匹配到路由,最后正确地渲染对应的组件。
这段描述看的晕晕的,history到底是个什么东西呢?这得从history API的出身说起。
history对象的诞生
我们都知道一个URL代表了网络上唯一的一个资源。这个资源可以是一个页面,一张图片等等。在地址栏里输入一个url地址,浏览器就会将对应的资源展示出来。当在不同的地址之间跳转时,我们很自然地想要回退或者前进一个地址。为了实现这个功能,浏览器厂商定义了history
对象。这时的history
对象大致有go()
、forward()
、back()
等方法,用于实现历史记录的访问、跳转。
使用 history API与浏览器历史记录进行交互
ajax的挑战
history
与 URL 就这样和谐的相处了一段时间,直到ajax技术的兴起。
浏览器有一个限制:如果你改变了URL,甚至是通过脚本,它就得向服务器发送请求,刷新整个页面。这很耗时,也耗资源,因为有时候2个页面长得差不多,仅仅有一小块地方不同。为了解决这个问题,ajax
技术兴起,利用ajax,可以不改变URL,只向服务器请求需要变动的数据,然后在前端通过DOM操作实现页面的局部更新。
ajax
极大提高了页面加载速度与用户体验。不过它同时带来了一个问题:同一个URL地址,可能会展示很多不同的信息,那么它作为唯一资源标识符的这个定义,在这里被破坏了。
有没有一个两全其美的办法呢?既可以实现页面的局部更新,又能够改变地址栏里的URL?
解决办法
井号方案
第一个跳出来解决问题的是URL中的#
。#
代表网页中的一个位置。它右边的字符就是页面中的位置标识符。例如下面的URL:
http://www.example.com/index.html#print
就代表网页index.html的print位置。浏览器读取这个URL后,会自动将print位置滚动至对应的区域。为网页位置指定标识符,有两个方法。一是使用锚点,比如,二是使用id属性,比如<div id=”print” >
#
的威力在于它是用来指导浏览器动作的,对服务器端完全无用。所以,HTTP请求中不包括#
,改变#
不触发网页重载。因此,如果我们在使用ajax局部刷新页面时,同时改变URL#
后面的标识符,就实现了局部刷新+改变URL了。
事实上,早前的twitter、google就是这么实现的。google甚至为此专门定义了一个搜索引擎优化的标识符#!
。可以参考这篇文章:URL的井号
history方案
URL毕竟是用于资源分享的,带个#
总感觉有点别扭。如果不用#
,有没有办法能够修改URL,同时又不向服务器发送请求呢?这就轮到history对象再次登场了。在HTML5规范中,W3C对history对象进行了一波升级:
HTML5 history API包括2个方法:history.pushState()和history.replaceState(),以及1个事件:window.onpopstate。
利用这2个方法,可以实现通过脚本修改浏览器中的URL地址,而不触发页面重载。采用这个方案,一个ajax请求过程是这样的:
- 通过脚本发送ajax请求到服务器,获取服务器响应数据
- 根据响应的数据操作DOM,更新页面内容
- 利用history.pushState()或replaceState()方法,修改地址栏中的URL
问题得到解决。
React Router的整合
在React Router中,将上述2个方案进行了整合,统一到了一个history库中。也就是我们看到的HashHistory
和BrowserHistory
。HashHistory
是上面井号方案的封装,BrowserHistory
则是对History方案的封装。特别需要注意到是,BrowserHistory需要对服务器端改造。
为什么?
考虑以下场景,我们用React Router的BrowserHistory开发了一个单页面应用,主页地址如下:
http://www.mysite.com/index
在index中可以点击链接进入欢迎页,详细介绍页等,地址为:
http://www.mysite.com/index/welcome
http://www.mysite.com/index/detail
如果我们在浏览器里直接输入主页面地址,然后通过页面里链接跳转到二级页面,一般是没有问题的。但是,如果在浏览器里直接输入http://www.mysite.com/index/welcome
会发生什么呢?
浏览器会认为这是一个URL请求,向服务器端请求这个URL代表的资源。但我们这是一个单页面应用,服务端只有一个index.html页面,没有跟这个地址匹配的资源,页面就会报错了。
因此,我们需要稍微改造一下服务端,将所有这样的请求都指向index.html页面,由前台处理路由信息。
总结
#
有话说:
为嘛,长得丑就要被淘汰吗!