React Router 的實現原理

天然呆☆☆發表於2024-11-15

本文分兩部分,一說前端路由的基本原理,二說 React Router 的實現原理

前端路由的基本原理

不說屁話,從時間線上講,Web 應用原本是後端渲染,後來隨著技術的發展,有了單頁面應用,慢慢從後端渲染髮展成前端渲染

在部落格前端路由hash、history的實現 一問中我已經介紹過這兩種模式

hash 模式

  • 透過監聽 hashchange 事件,匹配 hash 值並渲染頁面模組

history 模式

  • 利用 history.pushState + popState 實現
  • history.pushState 能實現不重新整理頁面,但往歷史棧中新增一個記錄
  • popState 則會在歷史記錄條目被更改時,觸發
  • pushState 只會改變歷史棧,修改它沒有什麼API可以監聽,所以要與 popState配合

簡單來說,前端路由的基本原理無非要實現兩個功能:

  • 監聽記錄路由變化
  • 匹配路由變化並渲染內容

hash模式

hash 模式的實現比較簡單,我們透過 hashChange 事件就能直接監聽到路由 hash 的變化,並根據匹配到的 hash 的不同來渲染不同的內容。

<body>
    <a href="#/home">Home</a>
    <a href="#/user">User</a>
    <a href="#/about">About</a>
    <div id="view"></div>
  </body>
  <script>
    function onHashChange() {
      const view = document.getElementById("view");
      switch (location.hash) {
        case "#/home":
          view.innerHTML = "Home";
          break;
        case "#/user":
          view.innerHTML = "User";
          break;
        case "#/about":
          view.innerHTML = "About";
          break;
        default:
          view.innerHTML = "Home";
          break;
      }
    }
    window.addEventListener("hashchange", onHashChange);
  </script>

history模式

  1. 攔截 a 標籤的點選事件,阻止它的預設跳轉行為
  2. 使用 H5 的 history API 更新 URL
  3. 監聽和匹配路由改變以更新頁面

利用 pushState 往歷史棧中新增記錄且不重新整理頁面的特性 + 監聽 popstate 瀏覽器操作導致的 URL 變化

  • window.history.pushState(state, title, path)

  • window.addEventListener("popstate", onPopState)

    <body>
        <a href="/home">Home</a>
        <a href="/user">User</a>
        <a href="/about">About</a>
        <div id="view"></div>
    </body>
    <script>
        const elements = document.querySelectorAll("a[href]");
        elements.forEach(el =>
          el.addEventListener("click", e => {
            e.preventDefault();
            const test = el.getAttribute("href");
            history.pushState(null, null, el.getAttribute("href"));
            onPopState();
          })
        );
    
        function onPopState() {
            const view = document.getElementById("view");
            switch (location.pathname) {
                case "/home":
                    view.innerHTML = "Home";
                    break;
                case "/user":
                    view.innerHTML = "User";
                    break;
                case "/about":
                    view.innerHTML = "About";
                    break;
                default:
                    view.innerHTML = "Home";
                    break;
            }
        }
        window.addEventListener("popstate", onPopState);
    </script>

hash模式 VS History模式

模式 優點 缺點
Hash 瀏覽器相容性更好,不需要後端路由支援 有 # 號
History 需要現代瀏覽器,需要後端路由支援 無 # 號

history 模式下當頁面重新整理時,因為找不到相對應的路由(因為只有一個頁面,路由由前端控制),所以會報404錯誤,需要在 Nginx(或者伺服器)中配置,如果找不到ur,則返回 首頁

nginx
location / {
    try_files $uri $uri/ /index.html;
}

React Router 的實現原理

先用最簡單的話來概括一下 React Router 到底做了什麼?

本質上, React Router 就是在頁面 URL 發生變化的時候,透過我們寫的 path 去匹配,然後渲染對應的元件。

那麼,我們想一下如何分步驟實現:

  1. 如何監聽 url 的變化 ?
  2. 如何匹配 path ?
  3. 渲染對應的元件

換句話說,也是一個元件,透過渲染不同的元件來控制路由切換

整體設計

我們用一張圖來理解一下整個 react-router 是怎麼實現的:

接下來我們看看每一個步驟是怎麼實現的。

640

如何監聽 url 的變化 ?

正常情況下,當 URL 發生變化時,瀏覽器會像服務端傳送請求,但使用以下 3 種辦法不會向服務端傳送請求:

  • 基於 ajax(實現起來很麻煩)
  • 基於 hash
  • 基於 history

react-router 使用了 history 這個核心庫。它本質是遮蔽不同模式下載監聽實現上的差異,使用釋出訂閱模式 來實現,這裡不做探究

總結

從後端路由到前端路由,最大的改變是體驗,體驗更良好了

前端路由模式有兩種:hash 模式 和 history 模式,兩者分別利用瀏覽器自由特性實現單頁面導航

  • hash 模式:window.location 或 a 標籤改變錨點值,window.hashchange() 監聽錨點變化
  • history 模式:history.pushState()、history.replaceState() 定義目標路由,window.popstate() 監聽瀏覽器操作導致的 URL 變化

React Router 匹配路由由 mathPath 透過 path-to-regexp 進行,<Route> 相當於一個高階元件,以不同的優先順序和匹配模式渲染匹配到的子元件

React Router 的主要元件原始碼,整體的實現:

  • 對於監聽功能的實現,React Router 引入了 history 庫,以遮蔽了不同模式路由在監聽實現上的差異, 並將路由資訊以 context 的形式,傳遞給被 <Router> 包裹的元件, 使所有被包裹在其中的路由元件都能感知到路由的變化, 並接收到路由資訊
  • 在匹配的部分, React Router 引入了 path-to-regexp 來拼接路徑正則以實現不同模式的匹配,路由元件·<Route>作為一個高階元件包裹業務元件, 透過比較當前路由資訊和傳入的 path,以不同的優先順序來渲染對應元件

參考資料

  • 深入淺出解析 React Router 原始碼

  • 單頁面應用路由實現原理:以 React-Router 為例

  • 剖析單頁面應用路由實現原理

  • SPA 路由三部曲之核心原理

  • SPA 路由三部曲之實戰演練

  • 圖解 React-router 帶你深入理解路由本質

相關文章