# 前端路由原理及实现方式

# 什么是前端路由

路由是根据不同的 URL 地址展示不同的内容或页面。路由的概念来源于服务器,在服务器中,路由描述的是 URL 与处理函数之间的映射关系。
而在 web 前端单页应用中,路由描述是 URL 和 UI 之间的映射关系,这种映射是单向的,即 URL 变化会引起 UI 更新(无需刷新页面)。
前端路由的兴起是把不同路由对应不同的内容或页面的任务交给前端做,之前是通过服务端根据 url 的不同返回不同的页面来实现的。

# 技术背景

通过记录 URL 来记录 web 页面板块上 Ajax 的变化,称为 Ajax 标签化,比较好的实现可参考 Pjax 等。 而对应较大的 framework,我们称为路由系统。

# 几个 H5 history Api

  • window.onhashchange 当一个窗口的 hash(URL 中#后面的部分)改变时,就会触发 hashchange 事件
  • window.history.length 声明浏览器历史列表中的元素数量
  • windiw.history.state 返回历史访问堆栈最顶端的记录状态,访问事件的 state 属性可获取当前pushState()replaceState()设置的状态数据
  • window.history.go([delta]) history.go(-1) 等同于 history.back();
  • window.history.back() 等同于点击浏览器的回退按钮
  • window.history.forward() 等价于 history.go(1);
  • window.history.pushState(data, title[url]) 改动站点路径并创建新的历史记录,仅改动 URL 的 hash 部分,window.location 使用的是同一个 document
  • window.history.replaceState(data, title[url] 改动站点路径但不创建新的历史记录
  • popstate事件:调用history.back(),history.forward(),history.go()等方法,会触发popstate事件,单纯调用pushState()replaceState()不触发popstate事件

这些是 Mozilla 在 HTML5 中实现的几个 History Api 的官方文档描述

# history.pushState 和 history.replaceState(data, title, url)

这两个 API 都会操作浏览器的历史栈,而不会引起页面刷新。 api 中的 data 参数,实际上是一个 state 对象,也即是 JavaScript 对象。Firefox 的实现中,它们是存在用户本地硬盘上的,最大支持到 640k,如果不够,按照 FF 的说法,可以用sessionStoragelocalStorage

// stateObj会被存入到stateObj
var stateObj = { foo: "bar" };
history.pushState(stateObj, "the blog", "name=next");
console.log(hisory.state); // 如果state是null,则是普通的历史记录

# popstate

window.onpopstate = function(event) {
  alert(
    "localtion: " +
      document.location +
      ", state: " +
      JSON.stringify(event.state)
  );
};
history.pushState({ page: 1 }, "title 1", "?page=1");
history.pushState({ page: 2 }, "title 2", "?page=2");
history.replaceState({ page: 3 }, "title 3", "?page=3");
history.back();
history.back();
history.go(2);

# 如何实现前端路由

路由大概的实现过程可以这么理解,对于高级浏览器,利用 H5 的新的 API 做好页面上不同板块 Ajax 等操作以 URL 的映射关系,甚至可以自己用 JAVAScript 手写一套历史栈管理模块,从而绕过浏览器自己的历史栈。 而当用户的操作触发 popstate 时,可以判断此时 url 与板块的映射关系,从而加载对应的 ajax 板块。

# hash 和 history

hash 的实现 hash 是 URL 中 hash(#)及后面的那部分,常用作锚点在页面内进行导航,改变 URL 中的 hash 部分不会引起页面的刷新,通过 hashchange 事件去监听 URL 的变化。改变 URL 的方式只有这几种:

  • 通过浏览器前进后退改变 URL
  • 通过标签改变 URL
  • 通过 window.location 改变 URL

history 实现 history 提供了 pushState 和 replaceState 两个方法,这两个方法改变 URL 和 path 部分不会引起页面刷新。 history 提供类似 hashchange 事件的 popstate 事件,但 popstate 事件有些不同

  • 通过浏览器前进后退改变 URL 时会触发 popstate 事件
  • 可以拦截pushState/replaceState的调用和标签的点击事件来检测 URL 的变化

# hash demo

<ul>
  <!-- 定义路由 -->
  <li><a href="#/home"></a></li>
  <li><a href="#/about"></a></li>
</ul>
<!-- 渲染对应路由 -->
<div id="routeView"></div>
// 页面加载完不会触发hashchange,这里会主动触发一次hashchange事件
window.addEventListener("DOMContentLoaded", onLoad);
// 监听路由变化
window.addEventListener("hashchange", onHashChange);

// 路由视图
var routerView = null;
function onLoad() {
  routerView = document.querySelector("#routeView");
  onHashChange();
}
function onHashChange() {
  switch (location.hash) {
    case "#/home":
      routerView.innerHTML = "home";
      return;
    case "#/ablout":
      routerView.innerHTML = "ablout";
      return;
    default:
      return;
  }
}

# history demo

<ul>
  <!-- 定义路由 -->
  <li><a href="#/home"></a></li>
  <li><a href="#/about"></a></li>
</ul>
<!-- 渲染对应路由 -->
<div id="routeView"></div>
// 页面加载完不会触发hashchange,这里会主动触发一次hashchange事件
window.addEventListener("DOMContentLoaded", onLoad);
window.addEventListener("popstate", onPopState);

var routerView = null;
function onLoad() {
  routerView = document.querySelector("#routeView");
  onPopState();
  // 拦截<a>标签点击事件默认行为,点击时使用pushState修改URL并更新手动UI,从而实现点击链接更新URL和UI效果
  var linkList = document.querySelectorAll("a[href]");
  linkList.forEach((el) =>
    el.addEventListener("click", function(e) {
      e.preventDefault();
      history.pushState(null, "", el.getAttribute("href"));
      onPopState();
    })
  );
}
function onPopState() {
  switch (location.pathname) {
    case "#/home":
      routerView.innerHTML = "home";
      return;
    case "#/ablout":
      routerView.innerHTML = "ablout";
      return;
    default:
      return;
  }
}

# Vue-Router 和 React-Router

Vue-Router

  • hash: 使用 URL hash 值来作为路由,默认模式
  • history: 依赖 HTML5 HistoryAPI 和服务器配置
  • abstract: 支持所有 JavaScript 运行环境,如 Node.js 服务端

React-Router

  • <HashRouter>: url 格式为 Hash 路由组件
  • <MemoryRouter>: 内存路由组件
  • <NativeRouter>: Native 的路由组件
  • <StaticRouter>: 地址不改变的静态路由组件