實作簡易的 Client Side Router
路由
一言蔽之,路由是匹配 URL 和 內容的流程。
以前,路由是由後端在處理。我們透過瀏覽器直到看到頁面的流程如下:
- 假設伺服器架在
godlike0108.github.io
這個網域 - 瀏覽器造訪伺服器下的某個 URL(例如: godlike0108.github.io/tetris )
- 伺服器路由解析
/tetris
路徑 - 伺服器回傳與
/tetris
路徑相對應的服務(執行函式、或回傳一個靜態頁面)
這麼做的壞處是,每次跳轉新頁面時,都得回傳一整個新的頁面文件;但有的時候,頁面間的差異可能僅僅是某些資料的不同,重新載入整份文件顯得浪費,也造成使用者體驗較差。
前端路由
後來有了 AJAX 技術,我們得以透過請求資料的方式,在相同頁面直接抽換部分內容,而不用透過後端路由載入整份新的頁面,單頁應用程式(Single Page Application, SPA)就發展了起來。
SPA 簡單來說,就是伺服器只提供給你單一頁面,要呈現不同內容,則以 AJAX 請求抽換內容來實現。
但有個問題,由於所有資料都在同一個 URL 底下,使用者可能在 SPA 內點啊點的,好不容易找到需要的資料,下次需要看同一筆資料時,不能透過 URL 直接連到該資料,還是只能進入首頁,點啊點的…
也就是說,「不同資料具有相對應的 URL 」還是有其必要性。因此,前端路由技術因應而生。
前端路由原理
相較於後端路由切換 URL 時會呼叫伺服器的服務來回傳頁面,前端路由切換 URL 時,是直接利用寫在前端的 JS 檔來替換 DOM 和資料。
因此合理的流程如下:
- 先定義各 URL 會對應到的處理函式
- 在切換 URL 時解析 URL 並觸發對應的處理函式
- 函式替換內容與 DOM
問題是:
- 程式碼怎麼改變 URL ?
- 程式碼怎麼知道 URL 何時被改變了?
因此我們需要一套能夠與 URL 互動的工具,而瀏覽器提供了兩種與 URL 互動的方式,分別為 Hash 跟 History API ,於是產生兩種不同的實作方式。
利用 Hash 實作前端路由
流程:
- 定義各 URL 會對應到的處理函式
- 用
window.location.hash
取得 URL - 用
<a href="#/<URL名稱>></a>
更動 URL 可觸發hashchange
事件 - 監聽
hashchange
事件,當事件觸發時執行與該 URL 對應的函式
原理:#
Hash 符號代表某個在頁面上的錨點,當造訪的 URL 是同頁面的錨點時,頁面不會重新載入。
若瀏覽器當前的 URL 內含有錨點時,使用 window.location.hash
能夠取得該錨點的字串值: #<錨點名稱>
。
瀏覽器提供 hashchange
事件,當錨點改變時會被觸發。
核心程式碼:
1 | // route handler |
利用 History API 實作前端路由
流程:
- 定義各 URL 會對應到的處理函式
- 用
window.location.pathname
取得 URL - 用
window.history.pushState()
、window.history.replaceState()
更動 URL 可觸發popstate
事件 - 監聽
popstate
事件,當事件觸發時執行與該 URL 對應的函式
原理:
目前主要瀏覽器都會提供 History API 讓我們能夠操縱當次瀏覽器訪問過的 URL 記錄。 與記錄相關的資料被存在 window.history
物件中。
history.pushState()
、 history.replaceState()
分別可以讓我們新增一個 URL 記錄、以及替換當前的 URL 記錄。被新增在記錄中的 URL ,用上一頁/下一頁可以瀏覽到。
當瀏覽狀態改變時, popstate
事件會被觸發,例如當「回到上一頁」、或造訪新的網站時。
很重要的特性是,更動瀏覽記錄並不等於瀏覽狀態改變,單純更動瀏覽記錄並不會讓瀏覽器直接跳轉到新的 URL 回傳的頁面,因此我們可以利用此特性製作前端路由。
核心程式碼:
1 | // route handler |