前言

这次的 why what or how 主题:单页应用的 router 模式,HashHistory是什么?

router 作为单页应用中重要的组成,有两种模式供我们选择,那么这两种模式的区别如何,分别的优势又在哪里?

想要深入对比两种模式的区别,我们首先要知道什么是路由?

什么是路由?

路由定义如下:路由为 URL 的路径,也就是 URL 中的 path 部分,一个路由对应一个具体的资源,可能是文件,可能是数据。

在单页应用出现之前,路由已在服务端被广泛使用,那么前端何时开始有了路由?

伴随着浏览器性能的提升、网速加快、Ajax 技术的成熟,部分前端程序员发现,使用 JavaScript 在前端维护一个模板,然后在用 Ajax 去服务端获取数据,就能够将模板和数据拼凑成一个动态的页面呈现给用户。随着这项技术的日渐成熟,一个问题渐渐的显示出来:如果 JavaScript 端维护了几个不同模板,用以在不同的状态下的呈现,该如何去维护这个状态?

路由!服务器就是用路由来区分不同资源,只需要把路由的概念引入前端开发中,用路由来标志这些状态!

那么如何引入?或者说如何实现?

模拟

路由的样子:一个形如 <path>?<query> 的字符串。

那么该如何存储这个路由呢?页面生成之初,我们所拥有的只有 URL。如果一开始就需要确定页面的状态,那就只能用这个 URL

首先分析下 URL 的组成:

<protocol>://<token>@<host>:<port>/<path>?<query>#<hash>

除去服务器需要使用的,能使用的仅剩下了 hash

虽然在浏览器中 hash 为一个锚点值,但却拥有以下特性:

  1. hash 值改变会记录历史记录。
  2. hash 值未匹配到元素时,页面并不会发生滚动。
  3. hash 值为一个任意的字符串。
  4. hash 的改变并会不让浏览器刷新页面。

那么现在再来看一个前端路由所需要的特性

  1. 需要记录历史。
  2. 一个类似 /a/b/c 的字符串。
  3. 路由改变不需要浏览器重新获取资源(也就是不刷新页面)。

这两者似乎是生来就该呆在一起,简直是完美契合。

有了思路,那我们使用 hash 来模拟一个简单的前端路由,如下:

http://test.com/app#/page/123

# 号前为需要请求服务器路由,# 后为前端路由。

看起来一切都挺完美?服务器浏览器各自处理各自的部分,JavaScript 中包含了所有的页面模板,以及路由映射表。但这就是最终的版本了吗?

是的!至少在 pushStatereplaceState 出现之前是。那么这两个 Api 效果是如何?为何会有这两个 Api

history Api

先来看看官方定义:

API 作用
pushStatus 向浏览器的历史记录中添加一条历史,同时将地址栏改为相应的地址,但却并不会导致浏览器刷新。
replaceState 直接替换地址栏的链接,不会产生新的历史,同样不会导致浏览器刷新。

需要注意的是,这两 API 仅能改变 URL<path>?<query> 部分,并不能改变其余部分。

看起来挺符合前端路由的概念的,甚至可以说为了配合前端路由诞生了这两个 API。但是问题真的解决了吗?

一切问题隐藏在用户主动刷新上。虽然说这两 API 屏蔽了浏览器的刷新,但用户主动触发的刷新却不能控制,思考以下场景:

  1. 用户打开 http://test.com/app
  2. 用户点击按钮导航到 http://test.com/app/page/123 由于是 API 控制,不会导致浏览器发出对应地址的请求。
  3. 用户主动刷新了页面。
  4. 浏览器发出 http://test.com/app/page/123 的请求,但服务器并没有对应的资源,导致 404

那么如何解决?由于问题发生在服务器,那只能在服务器上解决,nginx 重定向,或是让 http://test.com/app/page/123 返回和 http://test.com/app 一样的结果,那么其他页面怎么办,单页应用可不止有两个页面!写正则过滤请求!

优缺点

在说明优缺点时,把使用 hash 实现前端路由称为 hash 模式,把使用 history Api 称为 history 模式。

hash 模式

使用 hash 模拟路由,URL 格式如下

<protocol>://<token>@<host>:<port>/<path>?<query>#<path>?<query>

优点

  1. 职责明确,各部分处理各部分的事,互不干扰。
  2. 服务器不需要支持实现
  3. JavaScript 获取简单

缺点

  1. 带有 #/ 不精简,hash 部分的分享可能有问题。

history 模式

直接使用 URL<path>?<query>,格式与 URL 一致

<protocol>://<token>@<host>:<port>/<path>?<query>#<hash>

优点

  1. URL 简单,分享容易。
  2. hash 可以匹配 ID

缺点

  1. 服务器需要支持。
  2. 职责不明确,项目废弃后还需要去服务将相应的过滤去掉,会埋下一些隐藏 bug
  3. 如果有二级目录存在,请求过滤会比较复杂。

小结

个人喜欢使用 hash 模式,原因如下

  1. 开发简单。
  2. 现在分享基本上使用二维码,不用直接分享链接。
  3. 不会污染服务器。
  4. 锚点?什么是锚点?我不认识

最后,惯例提几个问题

  1. 什么是路由?
  2. 前端路由存在意义?
  3. hash 模式与 history 模式的区别?

最后的最后

该系列所有问题由 minimo 提出,爱你哟~~~