Hash 模式?history 模式?
前言
这次的 why what or how 主题:单页应用的 router 模式,Hash、History 是什么?
router 作为单页应用中的重要组成,有两种模式供我们选择。那么,这两种模式有何区别,各自的优势又在哪里?
想要深入对比两种模式的区别,我们首先要知道什么是路由?
什么是路由?
路由定义如下:路由是 URL 的路径,也就是 URL 中的 path 部分。一个路由对应一个具体资源,可能是文件,也可能是数据。
在单页应用出现之前,路由已在服务端被广泛使用,那么前端何时开始有了路由?
伴随着浏览器性能的提升、网速加快以及 Ajax 技术的成熟,部分前端程序员发现,使用 JavaScript 在前端维护模板,再用 Ajax 从服务端获取数据,就能将模板和数据拼成动态页面呈现给用户。随着这项技术日渐成熟,一个问题逐渐显现出来:如果 JavaScript 端维护了几个不同模板,用于呈现不同状态,该如何维护这些状态?
路由!服务器就是用路由区分不同资源,只需要把路由的概念引入前端开发,用路由来标志这些状态!
那么如何引入?或者说如何实现?
模拟
路由的样子:一个形如 <path>?<query> 的字符串。
那么该如何存储这个路由呢?页面生成之初,我们所拥有的只有 URL。如果一开始就需要确定页面的状态,那就只能用这个 URL。
首先分析一下 URL 的组成:
<protocol>://<token>@<host>:<port>/<path>?<query>#<hash>
除去服务器需要使用的,能使用的仅剩下了 hash。
虽然在浏览器中 hash 为一个锚点值,但却拥有以下特性:
hash值改变会记录历史记录。- 当
hash值未匹配到元素时,页面并不会发生滚动。 hash值为一个任意的字符串。hash的改变并不会让浏览器刷新页面。
那么,现在再来看一下前端路由所需要的特性:
- 需要记录历史。
- 一个类似
/a/b/c的字符串。 - 路由改变不需要浏览器重新获取资源(也就是不刷新页面)。
这两者似乎是生来就该呆在一起,简直是完美契合。
有了思路,那我们使用 hash 来模拟一个简单的前端路由,如下:
https://test.com/app#/page/123
# 号前是需要请求的服务器路由,# 号后是前端路由。
看起来一切都挺完美?服务器浏览器各自处理各自的部分,JavaScript 中包含了所有的页面模板,以及路由映射表。但这就是最终的版本了吗?
是的!至少在 pushState、replaceState 出现之前是。那么,这两个 API 效果如何?为何会有这两个 API?
History API
先来看看官方定义:
| API | 作用 |
|---|---|
pushState |
向浏览器的历史记录中添加一条记录,同时将地址栏改为相应的地址,但并不会导致浏览器刷新。 |
replaceState |
直接替换地址栏的链接,不会产生新的历史,同样不会导致浏览器刷新。 |
需要注意的是,这两个 API 仅能改变 URL 的 <path>?<query> 部分,并不能改变其余部分。
看起来挺符合前端路由的概念的,甚至可以说为了配合前端路由诞生了这两个 API。但是问题真的解决了吗?
问题隐藏在用户主动刷新上。虽然这两个 API 屏蔽了浏览器的刷新,但无法控制用户主动触发的刷新。思考以下场景:
- 用户打开
https://test.com/app。 - 用户点击按钮导航到
https://test.com/app/page/123。由于是API控制,不会导致浏览器发出对应地址的请求。 - 用户主动刷新了页面。
- 浏览器发出
https://test.com/app/page/123的请求,但服务器并没有对应的资源,导致404。
那么,如何解决?由于问题发生在服务器,只能在服务器上处理:使用 nginx 重定向,或让 https://test.com/app/page/123 返回和 https://test.com/app 一样的结果。那么其他页面怎么办?单页应用可不止有两个页面!写正则过滤请求!
优缺点
在说明优缺点时,把使用 hash 实现前端路由称为 hash 模式,把使用 History API 称为 history 模式。
hash 模式
使用 hash 模拟路由,URL 格式如下:
<protocol>://<token>@<host>:<port>/<path>?<query>#<path>?<query>
优点:
- 职责明确,各部分处理各部分的事,互不干扰。
- 服务器不需要额外支持。
JavaScript获取简单。
缺点:
- 带有
#/不精简,hash部分的分享可能有问题。
history 模式
直接使用 URL 的 <path>?<query>,格式与 URL 一致:
<protocol>://<token>@<host>:<port>/<path>?<query>#<hash>
优点:
URL简单,分享容易。hash可以匹配ID。
缺点:
- 服务器需要支持。
- 职责不明确,项目废弃后还需要去服务器上移除相应的过滤,会埋下一些隐藏的
bug。 - 如果有二级目录存在,请求过滤会比较复杂。
小结
个人喜欢使用 hash 模式,原因如下:
- 开发简单。
- 现在分享基本上使用二维码,不用直接分享链接。
- 不会污染服务器。
- 锚点?什么是锚点?我不认识。
最后,照例提几个问题:
- 什么是路由?
- 前端路由存在的意义是什么?
hash模式与history模式的区别?