单页应用的原理从早起的根据 url 的 hash 变化, 到根据 H5 的 history 的变化, 实现无刷新条件下的页面重新渲染. 那么在单页应用中是如何监听 url 的变化呢, 本文将总结一下, 如何在单页页面中优雅的监听 url 的变化.
单页应用原理
监听 url 中的 hash 变化
监听通过 history 来改变 url 的事件
replaceState 和 pushState 行为的监听
原文在我的博客中: https://github.com/forthealllight/blog/issues/37
欢迎 star
一, 单页应用原理
单页应用的原理, 在我们的上一篇文章中 React-Router 源码阅读 https://github.com/forthealllight/blog/issues/26 已经讲的很详细, 这里做一个简单介绍. 单页应用使得页面可以在无刷新的条件下重新渲染, 通过 hash 或者 html5 Bom 对象中的 history 可以做到改变 url, 但是不刷新页面.
(1) 通过 hash 来实现单页路由
早期的前端路由是通过 hash 来实现的:
改变 url 的 hash 值是不会刷新页面的.
因此可以通过 hash 来实现前端路由, 从而实现无刷新的效果. hash 属性位于 location 对象中, 在当前页面中, 可以通过:
Windows.location.hash='edit'
来实现改变当前 url 的 hash 值. 执行上述的 hash 赋值后, 页面的 url 发生改变.
赋值前: http://localhost:3000 赋值后: http://localhost:3000/#edit
在 url 中多了以 #结尾的 hash 值, 但是赋值前后虽然页面的 hash 值改变导致页面完整的 url 发生了改变, 但是页面是不会刷新的.
此外, 除了可以通过 Windows.location.hash 来改变当前页面的 hash 值外, 还可以通过 HTML 的 a 标签来实现:
<a href="#edit">edit</a>
(2) 通过 history 实现前端路由
HTML5 的 History 接口, History 对象是一个底层接口, 不继承于任何的接口. History 接口允许我们操作浏览器会话历史记录.
History 提供了一些属性和方法.
History 的属性:
History.length: 返回在会话历史中有多少条记录, 包含了当前会话页面. 此外如果打开一个新的 Tab, 那么这个 length 的值为 1
History.state: 保存了会出发 popState 事件的方法, 所传递过来的属性对象 (后面会在 pushState 和 replaceState 方法中详细的介绍)
History 方法:
History.back(): 返回浏览器会话历史中的上一页, 跟浏览器的回退按钮功能相同
History.forward(): 指向浏览器会话历史中的下一页, 跟浏览器的前进按钮相同
History.go(): 可以跳转到浏览器会话历史中的指定的某一个记录页
History.pushState():pushState 可以将给定的数据压入到浏览器会话历史栈中, 该方法接收 3 个参数, 对象, title 和一串 url.pushState 后会改变当前页面 url, 但是不会伴随着刷新
History.replaceState():replaceState 将当前的会话页面的 url 替换成指定的数据, replaceState 后也会改变当前页面的 url, 但是也不会刷新页面.
上面的方法中, pushState 和 repalce 的相同点:
就是都会改变当前页面显示的 url, 但都不会刷新页面.
不同点:
pushState 是压入浏览器的会话历史栈中, 会使得 History.length 加 1, 而 replaceState 是替换当前的这条会话历史, 因此不会增加 History.length.
(3) 总结
通过改变 hash 值, 或者 history 的 repalceState 和 pushState 都可以实现无刷新的改变 url. 这样还留有一个问题需要解决:
如何监听 url 的改变
因为我们不仅要无刷新的改变 url, 还要监听到这个 url 改变的行为, 根据该行为去重新渲染视图. 在下几章中, 重点介绍一下如何监听 url 的改变.
二, 监听 url 中的 hash 变化
通过 hash 改变了 url, 会触发 hashchange 事件, 只要监听 hashchange 事件, 就能捕获到通过 hash 改变 url 的行为.
- Windows.onhashchange=function(event){
- console.log(event);
- }
- // 或者
- Windows.addEventListener('hashchange',function(event){
- console.log(event);
- })
当 hash 值改变时, 输出一个 HashChangeEvent. 该 HashChangeEvent 的具体值为:
{isTrusted: true, oldURL: "http://localhost:3000/", newURL: "http://localhost:3000/#teg", type: "hashchange".....}
有了监听事件, 且改变 hash 页面不刷新, 这样我们就可以在监听事件的回调函数中, 执行我们展示和隐藏不同 UI 显示的功能, 从而实现前端路由.
三, 监听通过 history 来改变 url 的事件
在上一章讲到了通过 History 改变 url 有以下几种方法: History.back(),History.forward(),History.go(),History.pushState() 和 History.replaceState().
同时在 history 中还支持一个事件, 该事件为 popstate. 第一想法就是如果 popstate 能够监听所有的 history 方法所导致的 url 变化, 那么就大功告成了. 遗憾的是:
History.back(),History.forward(),History.go() 事件是会触发 popstate 事件的, 但是 History.pushState() 和 History.replaceState() 不会触发 popstate 事件.
如果是 History.back(),History.forward(),History.go() 那么会触发 popstate 事件, 我们只需要:
- Windows.addEventListener('popstate', function(event) {
- console.log(event);
- })
就可以监听到相应的行为, 手动调用:
- Windows.history.go();
- Windows.history.back();
- Windows.history.forward();
都会触发这个事件, 此外, 在浏览器中点击后退和前进按钮也会触发 popstate 事件, 这个事件内容为:
PopStateEvent {isTrusted: true, state: null, type: "popstate", target: Windows, currentTarget: Windows, ...}
但是, History.pushState() 和 History.replaceState() 不会触发 popstate 事件, 举例来说:
- Windows.addEventListener('popstate', function(event) {
- console.log(event);
- })
- Windows.history.pushState({first:'first'}, "page 2", "/first"})
上述例子中不会有任何的输出, 因为并没有监听的 popstate 事件的发生.
但是 History.go 和 History.back() 等, 虽然可以触发 popstate 事件, 但是都会刷新页面, 我们在单页应用中使用的是 replaceState 和 pushState, 因此这里还有一个等待解决的问题:
如何监听 replaceState 和 pushState 行为
四, replaceState 和 pushState 行为的监听
在上面的例子中我们发现 History.replaceState 和 pushState 确实不会触发 popstate 事件, 那么如何监听这两个行为呢. 可以通过在方法里面主动的去触发 popState 事件. 另一种就是在方法中创建一个新的全局事件.
具体做法为:
- var _wr = function(type) {
- var orig = history[type];
- return function() {
- var rv = orig.apply(this, arguments);
- var e = new Event(type);
- e.arguments = arguments;
- Windows.dispatchEvent(e);
- return rv;
- };
- };
- history.pushState = _wr('pushState');
- history.replaceState = _wr('replaceState');
这样就创建了 2 个全新的事件, 事件名为 pushState 和 replaceState, 我们就可以在全局监听:
- Windows.addEventListener('replaceState', function(e) {
- console.log('THEY DID IT AGAIN! replaceState 111111');
- });
- Windows.addEventListener('pushState', function(e) {
- console.log('THEY DID IT AGAIN! pushState 2222222');
- });
这样就可以监听到 pushState 和 replaceState 行为.
参考文章:
来源: https://juejin.im/post/5c26ec2f51882501cd6f497a