一文搞懂前端路由原理

前言

前端三大框架 AngularReactVue ,它們的路由解決方案 angular/routerreact-routervue-router 都是基於前端路由原理進行封裝實現的,因此將前端路由原理進行了解和掌握是很有必要的,因爲我們再使用的過程中也難免會遇到一些坑,一旦我們掌握了它的實現原理,那麼就能在開發中對路由的使用更加遊刃有餘。

一、什麼是路由?

路由的概念起源於服務端,在以前前後端不分離的時候,由後端來控制路由,當接收到客戶端發來的 HTTP 請求,就會根據所請求的相應 URL,來找到相應的映射函數,然後執行該函數,並將函數的返回值發送給客戶端。對於最簡單的靜態資源服務器,可以認爲,所有 URL 的映射函數就是一個文件讀取操作。對於動態資源,映射函數可能是一個數據庫讀取操作,也可能是進行一些數據的處理等等。然後根據這些讀取的數據,在服務器端就使用相應的模板來對頁面進行渲染後,再返回渲染完畢的頁面。它的好處與缺點非常明顯:

也正是由於後端路由還存在着自己的不足,前端路由纔有了自己的發展空間。對於前端路由來說,路由的映射函數通常是進行一些 DOM 的顯示和隱藏操作。這樣,當訪問不同的路徑的時候,會顯示不同的頁面組件。前端路由主要有以下兩種實現方案:

當然,前端路由也存在缺陷:使用瀏覽器的前進,後退鍵時會重新發送請求,來獲取數據,沒有合理地利用緩存。但總的來說,現在前端路由已經是實現路由的主要方式了,前端三大框架 AngularReactVue ,它們的路由解決方案 angular/routerreact-routervue-router 都是基於前端路由進行開發的,因此將前端路由進行了解和 掌握是很有必要的,下面我們分別對兩種常見的前端路由模式 Hash 和 History 進行講解。

二、前端路由的兩種實現

2.1、Hash 模式

2.1.1、原理

早期的前端路由的實現就是基於 location.hash 來實現的。其實現原理也很簡單,location.hash 的值就是 URL 中 # 後面的內容。比如下面這個網站,它的 location.hash 的值爲 '#search'

https://www.word.com#search

此外,hash 也存在下面幾個特性:

我們可以通過兩種方式觸發 hash 變化,一種是通過 a 標籤,並設置 href 屬性,當用戶點擊這個標籤後,URL 就會發生改變,也就會觸發 hashchange 事件了:

<a href="#search">search</a>

還有一種方式就是直接使用 JavaScript來對 loaction.hash 進行賦值,從而改變 URL,觸發 hashchange 事件:

location.hash="#search"

以下實現我們採用第 2 種方式來實現。

2.1.2、實現

我們先定義一個父類 BaseRouter,用於實現 Hash 路由和 History 路由的一些共有方法;

export class BaseRouter {
  // list 表示路由表
  constructor(list) {
    this.list = list;
  }
  // 頁面渲染函數
  render(state) {
    let ele = this.list.find(ele => ele.path === state);
    ele = ele ? ele : this.list.find(ele => ele.path === '*');
    ELEMENT.innerText = ele.component;
  }
}

我們簡單實現了 push 壓入功能、go 前進 / 後退功能,相關代碼的註釋都已經標上,簡單易懂,就不在一 一介紹,參見如下:

export class HashRouter extends BaseRouter {
  constructor(list) {
    super(list);
    this.handler();
    // 監聽 hashchange 事件
    window.addEventListener('hashchange', e => {
      this.handler();
    });
  }
  // hash 改變時,重新渲染頁面
  handler() {
    this.render(this.getState());
  }
  // 獲取 hash 值
  getState() {
    const hash = window.location.hash;
    return hash ? hash.slice(1) : '/';
  }
  // push 新的頁面
  push(path) {
    window.location.hash = path;
  }
  // 獲取 默認頁 url
  getUrl(path) {
    const href = window.location.href;
    const i = href.indexOf('#');
    const base = i >= 0 ? href.slice(0, i) : href;
    return base +'#'+ path;
  }
  // 替換頁面
  replace(path) {
    window.location.replace(this.getUrl(path));
  }
  // 前進 or 後退瀏覽歷史
  go(n) {
    window.history.go(n);
  }
}

2.1.3、效果圖

Hash 模式的路由實現例子的效果圖如下所示:

2.2、History 模式

2.2.1、原理

前面的 hash 雖然也很不錯,但使用時都需要加上 #,並不是很美觀。因此到了 HTML5,又提供了 History API 來實現 URL 的變化。其中做最主要的 API 有以下兩個:history.pushState() 和 history.repalceState()。這兩個 API可以在不進行刷新的情況下,操作瀏覽器的歷史紀錄。唯一不同的是,前者是新增一個歷史記錄,後者是直接替換當前的歷史記錄,如下所示:

window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);

此外,history 存在下面幾個特性:

2.2.2、實現

我們同樣簡單實現了 push 壓入功能、go 前進 / 後退功能,相關代碼的註釋都已經標上,簡單易懂,就不在一 一介紹,參見如下:

export class HistoryRouter extends BaseRouter {
  constructor(list) {
    super(list);
    this.handler();
    // 監聽 popstate 事件
    window.addEventListener('popstate', e => {
      console.log('觸發 popstate。。。');
      this.handler();
    });
  }
  // 渲染頁面
  handler() {
    this.render(this.getState());
  }
  // 獲取 url
  getState() {
    const path = window.location.pathname;
    return path ? path : '/';
  }
  // push 頁面
  push(path) {
    history.pushState(null, null, path);
    this.handler();
  }
  // replace 頁面
  replace(path) {
    history.replaceState(null, null, path);
    this.handler();
  }
   // 前進 or 後退瀏覽歷史
  go(n) {
    window.history.go(n);
  }
}

2.2.3、效果圖

History 模式的路由實現例子的效果圖如下所示:

2.3、兩種路由模式的對比

hZYZVH

三、總結

本文我們大致介紹了什麼是路由、前端路由的源起、以及分析了兩種前端路由:Hash 模式和 History 模式的原理以及簡單功能實現。 通過本文對前端路由原理的掌握,這時你就可以基於原理基礎去閱讀 vue-router 和 react-router 的源碼實現了。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/BzoFlQq59iMWQFPxLUJavw