前端处理动态 url 和 pushStatus 的使用

2022-03-22 10:33:55 浏览数 (1)

起因

起因是这样的,在尝试前后端分离的这条道路上,我自己也在不断摸索,感觉要把大部分的坑都踩踩了。目前我用的技术是:

  • webpack 自动构建
  • AMD 模块化 js
  • Sass 预处理 CSS
  • 使用前端模板引擎 handlebars 解决动态操作将 html 拼接在 js 中的问题

但最近写了一个项目类似知乎这样的多页网站。前端 url 的处理让我觉得不够优雅。我使用的是 hash 的方式处理动态 url 的,为此我专门在知乎上提了一个问题:前端如何处理动态url?

这里我将问题描述如下:

前后端彻底分离的情况下,页面跳转页全部由前端控制。那么如何更好的处理动态url地址? 例如本问题的url为 https://www.zhihu.com/question/38802932 这肯定是用后台路由处理的url 纯前端怎么处理?用hash吗,如下: https://www.zhihu.com/question#38802932 那如果本页跳转,只改变hash的话,页面不会刷新。 使用location.reload()倒是可以解决。 但总觉得这样处理不够优雅。大家在工作中是如何处理此类场景的?还是用传统的后台路由来提供动态url?

感谢郑海波和剧中人的热心回答。都提到了history对象中的pushState,这是我第一次接触到这方面的内容(顿时觉得自己真是才疏学浅)。

同时也有人提到了pjax,这个就是pushState Ajax的封装,也很有意思。

下面就来研究和实践一下吧。

History

window对象通过history对象提供对浏览器历史记录的访问能力。它暴露了一些非常有用的方法和属性,让你在历史记录中自由前进和后退,而在 HTML5 中,更可以操纵历史记录中的数据。

back(), forward(), go(), length

浏览器的历史记录就好像一个栈,最新的在最上面,较早之前看过的在下面

下面介绍怎么在这些历史记录中跳转,但要注意,上图中的浏览器历史记录和本文说的 history 还不太同。

back()

代码语言:javascript复制
  history.back();

forward()

代码语言:javascript复制
  history.forward();

go()

代码语言:javascript复制
  history.go(-1);

go()不填参数或参数为go(0)时,页面会刷新,即history.go()history.go(0)相当于location.reload()

length

本节在线demo见:History & pjax demo 源代码:

经过亲自测试,history对象只记录同一个 tab 页内的历史。如果是在新窗口打开的,则无效。如:在a标签中添加target="_blank",或按住ctrl点击,这类场景下,在新的tab页中,history对象也是新的。

history对象记录的信息与是否同源也无关,所以唯一要满足的就是同一个标签页。

pushState(), replaceState()

HTML5 引进了history.pushState()方法和history.replaceState()方法,它们允许你逐条地添加和修改历史记录条目,能够在不加载新页面的情况下没改变浏览器的URL。这些方法可以协同window.onpopstate事件一起工作。

使用history.pushState()会改变referrer的值,而在你调用方法后创建的 XMLHttpRequest 对象会在 HTTP 请求头中使用这个值。referrer的值则是创建 XMLHttpRequest 对象时所处的窗口的 URL。

pushState(any data, string title, [string url])

执行pushState后,可以在不加载新页面的情况下,更改url。同时history栈中新增一条数据。

代码语言:javascript复制
  <button id="push1">pushState()</button>

  document.querySelector('#push1').addEventListener('click', function() {
      history.pushState('abc','pushStatePageTitle','pushState.html');
      document.querySelector('#length').innerHTML = history.length;//重新读取历史长度
  });
  • replaceState(any data, string title, [string url])pushState()类似,只是在history栈中不是新增记录,而是替换一条记录。

需要注意的是:pushState()replaceState()方法存在安全方面的限制,本地测试是无效的,会报错,可以简单放到任何服务端测试,或者使用http-server开启简单服务器,通过访问localhost来查看效果。

本节demo见:History & pjax demo - pushState

pjax

现在再看本文一开始提出的问题,如何让前端优雅的控制 url,这里就可以考虑 pjax 技术了。我们把 pushState ajax 进行封装,合起来简称为 pjax。虽然不是什么新的技术,但概念已然不同。

如果不使用 pjax。我们依然可以使用hash来实现文本开始的需求。但会不利于 SEO,看着也不够优雅。

Pjax的原理十分简单。

  1. 拦截 a 标签的默认跳转动作或某些按钮的点击事件。
  2. 使用 Ajax 请求新页面。
  3. 将返回的 Html 替换到页面中。
  4. 使用 HTML5 的pushState()修改Url。

个人理解3中也可以仅仅请求数据,再由浏览器渲染。

每当同一个文档的浏览历史(即history对象)出现变化时,会触发window.onpopstate事件。

代码语言:javascript复制
window.onpopstate = function(event) {
    console.log(event.state);
    console.log(location);
};

这样在用户点击前进后退时也可以很好的监听url,来做相应的页面渲染。

若用户刷新了页面,但没有相应的页面资源,这时页面就会显示不存在。所以我认为较好的方法是在写pushState()第三个参数的时候,写为?a=1这样的参数形式。History.js 也是这么写的。但是这样应该会多一次请求。也许使用 nodeJS 作为中间层会好一些吧。

对于上述的探索,不知道是不是我还不够深入,总觉得还是不够完美。

参考

  • MDN History
  • MDN 操纵浏览器的历史记录
  • pjax 是如何工作的? 知乎
  • PJAX的实现与应用 小胡子哥
  • URL的井号-阮一峰
  • history对象 JavaScript 标准参考教程(alpha) 阮一峰
  • Pjax(pushState and Ajax) 黯羽轻扬
  • 操纵历史,利用HTML5 History API实现无刷新跳转 蓝飞
  • 前端:将网站打造成单页面应用SPA(一) Coffce
  • coffce-pjax
  • History.js
  • defunkt/jquery-pjax GitHub
  • welefen/pjax

0 人点赞