一文搞懂前端路由原理
前言
前端三大框架 Angular、React、Vue ,它們的路由解決方案 angular/router、react-router、vue-router 都是基於前端路由原理進行封裝實現的,因此將前端路由原理進行了解和掌握是很有必要的,因爲我們再使用的過程中也難免會遇到一些坑,一旦我們掌握了它的實現原理,那麼就能在開發中對路由的使用更加遊刃有餘。
一、什麼是路由?
路由的概念起源於服務端,在以前前後端不分離的時候,由後端來控制路由,當接收到客戶端發來的 HTTP 請求,就會根據所請求的相應 URL,來找到相應的映射函數,然後執行該函數,並將函數的返回值發送給客戶端。對於最簡單的靜態資源服務器,可以認爲,所有 URL 的映射函數就是一個文件讀取操作。對於動態資源,映射函數可能是一個數據庫讀取操作,也可能是進行一些數據的處理等等。然後根據這些讀取的數據,在服務器端就使用相應的模板來對頁面進行渲染後,再返回渲染完畢的頁面。它的好處與缺點非常明顯:
-
好處:安全性好,
SEO好; -
缺點:加大服務器的壓力,不利於用戶體驗,代碼冗合不好維護;
也正是由於後端路由還存在着自己的不足,前端路由纔有了自己的發展空間。對於前端路由來說,路由的映射函數通常是進行一些 DOM 的顯示和隱藏操作。這樣,當訪問不同的路徑的時候,會顯示不同的頁面組件。前端路由主要有以下兩種實現方案:
-
Hash -
History
當然,前端路由也存在缺陷:使用瀏覽器的前進,後退鍵時會重新發送請求,來獲取數據,沒有合理地利用緩存。但總的來說,現在前端路由已經是實現路由的主要方式了,前端三大框架 Angular、React、Vue ,它們的路由解決方案 angular/router、react-router、vue-router 都是基於前端路由進行開發的,因此將前端路由進行了解和 掌握是很有必要的,下面我們分別對兩種常見的前端路由模式 Hash 和 History 進行講解。
二、前端路由的兩種實現
2.1、Hash 模式
2.1.1、原理
早期的前端路由的實現就是基於 location.hash 來實現的。其實現原理也很簡單,location.hash 的值就是 URL 中 # 後面的內容。比如下面這個網站,它的 location.hash 的值爲 '#search':
https://www.word.com#search
此外,hash 也存在下面幾個特性:
-
URL中hash值只是客戶端的一種狀態,也就是說當向服務器端發出請求時,hash部分不會被髮送。 -
hash值的改變,都會在瀏覽器的訪問歷史中增加一個記錄。因此我們能通過瀏覽器的回退、前進按鈕控制hash的切換。 -
我們可以使用
hashchange事件來監聽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 存在下面幾個特性:
-
pushState和repalceState的標題(title):一般瀏覽器會忽略,最好傳入null; -
我們可以使用
popstate事件來監聽url的變化; -
history.pushState()或history.replaceState()不會觸發popstate事件,這時我們需要手動觸發頁面渲染;
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、兩種路由模式的對比
三、總結
本文我們大致介紹了什麼是路由、前端路由的源起、以及分析了兩種前端路由:Hash 模式和 History 模式的原理以及簡單功能實現。 通過本文對前端路由原理的掌握,這時你就可以基於原理基礎去閱讀 vue-router 和 react-router 的源碼實現了。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/BzoFlQq59iMWQFPxLUJavw