写在前面
现在的前端应用很多都是单页应用。路由对于单页应用来说,是一个重要的组成部分。本系列文章将讲解前端路由的实现原理。这是系列文章的第二篇:history路由。
系列文章第一篇:前端路由解析(一) —— hash路由
history api
HTML5引入了history.pushState
和history.replaceState
方法,他们分别可以添加和修改历史记录条目。pushState
和replaceState
都接收三个参数:
- state: 一个JS对象。在
popstate
事件中使用。 - title: 标题。一般浏览器都忽略这个参数。
- url: 任意url,可以是相对路径,也可以是绝对路径。但是必须与当前url同源。
一个pushState的例子,假如我们在https://www.google.com/执行以下代码:
let stateObj = { foo: "bar",}; history.pushState(stateObj, "page 2", "bar.html");
浏览器的地址栏将变成:https://www.google.com/bar.html
但是,浏览器并不会刷新。此时,history.state
的值为:{foo: "bar"}
我们点击浏览器的后退按钮,浏览器会重新回到https://www.google.com/
replaceState
和pushState
的用法一样,仅有一处不同:
history.pushState是在保留当前记录记录的基础上,再添加一个新的url。而history.replaceState是将当前历史记录替换为新的url。
什么时候会触发popstate事件呢?无论什么时候用户导航到新的状态,popstate事件就会被触发。举个例子,我们在某个页面执行下面的代码:
history.pushState({ page: 1 }, '', 'page1.html');history.pushState({ page: 2 }, '', 'page2.html');
执行完成后,页面会链接为:xxxx/page2.html
此时,点击浏览器后退按钮,页面会回到 xxxx/page1.html,并且会触发一次popstate事件,且事件对象的state参数值为:{ page: 1}
。再次点击浏览器历史记录前进按钮,页面回到 xxxx/page2.html,触发一次popstate事件,事件对象的state参数值为:{page : 2}
。
history路由
history路由就是通过history.pushState
,history.replaceState
以及 popstate
来实现的。我们实现一个最简单的,包含下面两个方面功能:
1、用户点击a标签,可以进行路由跳转。
2、用户点击浏览器的前进后退按钮,可以进行路由跳转。
<html> <body> <a href='/p1'>page1</a> <a href='/p2'>page2</a> <a href='/p3'>page3</a> <main> <div id="content"> 这是首页 </div> </main> </body> <script> window.onload = function () { const routerMap = { '/p1': () => { document.getElementById('content').innerHTML = '这是段落1111的内容' }, '/p2': () => { document.getElementById('content').innerHTML = '这是段落22222的内容' }, '/p3': () => { document.getElementById('content').innerHTML = '这是段落33333的内容' }, }; const handleRoute = function(inputUrl) { const url = '/' + inputUrl; const routeCb = routerMap[url]; if(typeof routeCb === 'function') { routeCb(); } } window.onpopstate = function (e) { const url = e.state && e.state.url || ''; url && handleRoute(url); } document.addEventListener('click', e => { if(e.target.tagName !== 'A') return; e.preventDefault(); const href = e.target.getAttribute('href'); if(!href) return; const url = href.split('/').pop(); window.history.pushState({ url }, null , url); handleRoute(url); }); }</script></html>
最终的效果如下:
这里需要注意的是,history路由往往需要服务端的配合。虽说 history api 不会刷新页面,但是当用户处在某一个路由并刷新页面时,或者用户直接访问带有路由的页面时,如果服务端没有对当前url进行处理,那么很可能就直接404了。所以需要服务端把相关的url,全都匹配到对应的单页应用的html上。
history路由和hash路由对比
结合之前的hash路由原理,我们可以简单对比一下:
1、history路由比hash路由"长"得好看一下,毕竟url里面带有#
看起来不太美观。
2、history路由兼容性不够完美,毕竟是使用了html5标准的api,并且,history路由往往需要服务端的配合。
3、hash路由的兼容性更好。
在实际项目中,根据自己的实际情况来选择使用哪种路由就好了。
写在后面
本文解析了history api,以及history路由的实现原理,符合预期。后续会对react-router
的源码进行解析。