一文搞懂前端路由原理
前言
前端三大框架 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