聊聊React Router中的History

在学习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请求过程是这样的:

  1. 通过脚本发送ajax请求到服务器,获取服务器响应数据
  2. 根据响应的数据操作DOM,更新页面内容
  3. 利用history.pushState()或replaceState()方法修改地址栏中的URL

问题得到解决

React Router的整合

在React Router中,将上述2个方案进行了整合,统一到了一个history库中。也就是我们看到的HashHistorybrowserHistoryHashHistory是上面井号方案的封装,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页面,由前台处理路由信息。

总结

#有话说:

为嘛,长得丑就要被淘汰吗!

参考资料

相关文章

react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接...
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc ...