一文瞭解 history 和 react-router 的實現原理

来源:https://www.cnblogs.com/dtux/archive/2023/08/17/17636773.html
-Advertisement-
Play Games

>我們是[袋鼠雲數棧 UED 團隊](http://ued.dtstack.cn/),致力於打造優秀的一站式數據中台產品。我們始終保持工匠精神,探索前端道路,為社區積累並傳播經驗價值。 >本文作者:[霜序](https://luckyfbb.github.io/blog) ## 前言 在[前一篇文章 ...


我們是袋鼠雲數棧 UED 團隊,致力於打造優秀的一站式數據中台產品。我們始終保持工匠精神,探索前端道路,為社區積累並傳播經驗價值。

本文作者:霜序

前言

前一篇文章中,我們詳細的說了 [email protected] 升級到 @6.x 需要註意的問題以及變更的使用方式。

react-router 版本更新非常快,但是它的底層實現原理確是萬變不離其中,在本文中會從前端路由出發到 react-router 原理總結與分享。

前端路由

在 Web 前端單頁面應用 SPA(Single Page Application)中,路由是描述 URL 和 UI 之間的映射關係,這種映射是單向的,即 URL 的改變會引起 UI 更新,無需刷新頁面

如何實現前端路由

實現前端路由,需要解決兩個核心問題

  1. 如何改變 URL 卻不引起頁面刷新?
  2. 如何監測 URL 變化?

在前端路由的實現模式有兩種模式,hash 和 history 模式,分別回答上述兩個問題

hash 模式

  1. hash 是 url 中 hash(#) 及後面的部分,常用錨點在頁面內做導航,改變 url 中的 hash 部分不會引起頁面的刷新
  2. 通過 hashchange 事件監聽 URL 的改變。改變 URL 的方式只有以下幾種:通過瀏覽器導航欄的前進後退、通過<a>標簽、通過window.location,這幾種方式都會觸發hashchange事件

history 模式

  1. history 提供了 pushStatereplaceState 兩個方法,這兩個方法改變 URL 的 path 部分不會引起頁面刷新
  2. 通過 popchange 事件監聽 URL 的改變。需要註意只在通過瀏覽器導航欄的前進後退改變 URL 時會觸發popstate事件,通過<a>標簽和pushState/replaceState不會觸發popstate方法。但我們可以攔截<a>標簽的點擊事件和pushState/replaceState的調用來檢測 URL 變化,也是可以達到監聽 URL 的變化,相對hashchange顯得略微複雜

JS 實現前端路由

基於 hash 實現

由於三種改變 hash 的方式都會觸發hashchange方法,所以只需要監聽hashchange方法。需要在DOMContentLoaded後,處理一下預設的 hash 值

// 頁面載入完不會觸發 hashchange,這裡主動觸發一次 hashchange 事件,處理預設hash
window.addEventListener('DOMContentLoaded', onLoad);
// 監聽路由變化
window.addEventListener('hashchange', onHashChange);
// 路由變化時,根據路由渲染對應 UI
function onHashChange() {
  switch (location.hash) {
    case '#/home':
      routerView.innerHTML = 'This is Home';
      return;
    case '#/about':
      routerView.innerHTML = 'This is About';
      return;
    case '#/list':
      routerView.innerHTML = 'This is List';
      return;
    default:
      routerView.innerHTML = 'Not Found';
      return;
  }
}

hash 實現 demo

基於 history 實現

因為 history 模式下,<a>標簽和pushState/replaceState不會觸發popstate方法,我們需要對<a>的跳轉和pushState/replaceState做特殊處理。

  • <a>作點擊事件,禁用預設行為,調用pushState方法並手動觸發popstate的監聽事件
  • pushState/replaceState可以重寫 history 的方法並通過派發事件能夠監聽對應事件
var _wr = function (type) {
  var orig = history[type];
  return function () {
    var e = new Event(type);
    e.arguments = arguments;
    var rv = orig.apply(this, arguments);
    window.dispatchEvent(e);
    return rv;
  };
};
// 重寫pushstate事件
history.pushState = _wr('pushstate');

function onLoad() {
  routerView = document.querySelector('#routeView');
  onPopState();
  // 攔截 <a> 標簽點擊事件預設行為
  // 點擊時使用 pushState 修改 URL並更新手動 UI,從而實現點擊鏈接更新 URL 和 UI 的效果。
  var linkList = document.querySelectorAll('a[href]');
  linkList.forEach((el) =>
    el.addEventListener('click', function (e) {
      e.preventDefault();
      history.pushState(null, '', el.getAttribute('href'));
      onPopState();
    }),
  );
}
// 監聽pushstate方法
window.addEventListener('pushstate', onPopState());
// 頁面載入完不會觸發 hashchange,這裡主動觸發一次 popstate 事件,處理預設pathname
window.addEventListener('DOMContentLoaded', onLoad);
// 監聽路由變化
window.addEventListener('popstate', onPopState);
// 路由變化時,根據路由渲染對應 UI
function onPopState() {
  switch (location.pathname) {
    case '/home':
      routerView.innerHTML = 'This is Home';
      return;
    case '/about':
      routerView.innerHTML = 'This is About';
      return;
    case '/list':
      routerView.innerHTML = 'This is List';
      return;
    default:
      routerView.innerHTML = 'Not Found';
      return;
  }
}

history 實現 demo

React-Router 的架構

file

  • history 庫給 browser、hash 兩種 history 提供了統一的 API,給到 react-router-dom 使用
  • react-router 實現了路由的最核心能力。提供了<Router><Route>等組件,以及配套 hook
  • react-router-dom 是對 react-router 更上一層封裝。把 history 傳入<Router>並初始化成<BrowserRouter><HashRouter>,補充了<Link>這樣給瀏覽器直接用的組件。同時把 react-router 直接導出,減少依賴

History 實現

history

在上文中說到,BrowserRouter使用 history 庫提供的createBrowserHistory創建的history對象改變路由狀態和監聽路由變化。

❓ 那麼 history 對象需要提供哪些功能訥?

  • 監聽路由變化的listen方法以及對應的清理監聽unlisten方法
  • 改變路由的push方法
// 創建和管理listeners的方法
export const EventEmitter = () => {
  const events = [];
  return {
    subscribe(fn) {
      events.push(fn);
      return function () {
        events = events.filter((handler) => handler !== fn);
      };
    },
    emit(arg) {
      events.forEach((fn) => fn && fn(arg));
    },
  };
};

BrowserHistory

const createBrowserHistory = () => {
  const EventBus = EventEmitter();
  // 初始化location
  let location = {
    pathname: '/',
  };
  // 路由變化時的回調
  const handlePop = function () {
    const currentLocation = {
      pathname: window.location.pathname,
    };
    EventBus.emit(currentLocation); // 路由變化時執行回調
  };
  // 定義history.push方法
  const push = (path) => {
    const history = window.history;
    // 為了保持state棧的一致性
    history.pushState(null, '', path);
    // 由於push並不觸發popstate,我們需要手動調用回調函數
    location = { pathname: path };
    EventBus.emit(location);
  };

  const listen = (listener) => EventBus.subscribe(listener);

  // 處理瀏覽器的前進後退
  window.addEventListener('popstate', handlePop);

  // 返回history
  const history = {
    location,
    listen,
    push,
  };
  return history;
};

對於 BrowserHistory 來說,我們的處理需要增加一項,當我們觸發 push 的時候,需要手動通知所有的監聽者,因為 pushState 無法觸發 popState 事件,因此需要手動觸發

HashHistory

const createHashHistory = () => {
  const EventBus = EventEmitter();
  let location = {
    pathname: '/',
  };
  // 路由變化時的回調
  const handlePop = function () {
    const currentLocation = {
      pathname: window.location.hash.slice(1),
    };
    EventBus.emit(currentLocation); // 路由變化時執行回調
  };
  // 不用手動執行回調,因為hash改變會觸發hashchange事件
  const push = (path) => (window.location.hash = path);
  const listen = (listener: Function) => EventBus.subscribe(listener);
  // 監聽hashchange事件
  window.addEventListener('hashchange', handlePop);
  // 返回的history上有個listen方法
  const history = {
    location,
    listen,
    push,
  };
  return history;
};

在實現 hashHistory 的時候,我們只是對hashchange進行了監聽,當該事件發生時,我們獲取到最新的 location 對象,在通知所有的監聽者 listener 執行回調函數

React-Router@6 丐版實現

file

  • 綠色為 history 中的方法
  • 紫色為 react-router-dom 中的方法
  • 橙色為 react-router 中的方法

Router


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文分享自華為雲社區《【手把手帶你玩轉HetuEngine】(一)HetuEngine快速上手》,作者:HetuEngine九級代言。 HetuEngine是什麼 HetuEngine是華為推出的高性能互動式SQL分析及數據虛擬化引擎。與大數據生態無縫融合,實現海量數據秒級互動式查詢;支持跨源跨域統 ...
  • Taier 作為[袋鼠雲](https://www.dtstack.com/?src=szsm)的[開源項目](https://www.dtstack.com/?src=szsm)之一,是一個[分散式可視化的 DAG 任務調度系統](https://www.dtstack.com/?src=szsm ...
  • ##### 3 列表標簽 html中的列表標簽,該類標簽是關於HTML文檔中列表的,包含dl、dt、dd、ol、li、ul等標簽。這裡主要說的是ul和ol標簽。 (1)ol標簽代表HTML的有序列表。ol成對出現,以開始,結束。列表中的每一列使用標簽定義,這一點與無序列表相同。每列使用數字或字母開頭 ...
  • ##### 2 超鏈接標簽 超鏈接是瀏覽者和伺服器的交互的主要手段,也叫超級鏈接或a鏈接,是網頁中指向一個目標的連接關係,這個目標可以是網頁、網頁中的具體位置、圖片、郵件地址、文件、應用程式等。 超鏈接是網頁中最重要的元素之一。一個網站的各個網頁就是通過超鏈接關聯起來的,用戶通過點擊超鏈接可以從一個 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 每次用vite創建項目秒建好,前幾天用vue-cli創建了一個項目,足足等了我一分鐘,那為什麼用 vite 比 webpack 要快呢,這篇文章帶你梳理清楚它們的原理及不同之處!文章有一點長,看完絕對有收穫! 正文 一、webpac ...
  • ### 一、實現效果 + 點擊全選按鈕/取消全選,控制商品的全選或取消 + 每個商品的覆選框都選中後,自動勾上全選按鈕,或者商品任何一個覆選框沒有選中,取消全選 ![image](https://img2023.cnblogs.com/blog/2408012/202308/2408012-2023 ...
  • ```html 1基本標簽 你是風兒我是沙 你是風兒我是沙 你是風兒我是沙 你是風兒我是沙 你是風兒我是沙 你是風兒我是沙 床前明月光, 疑是地上霜. 舉頭望明月, 低頭思故鄉. 大家好,我是段落標簽p。我按了enter一下 換行了 大家好,我是段落標簽p。我按了enter一下 換行了 定義粗體文本 ...
  • 本文主要講述京東門詳業務在支撐過程中遇到的困境,面對問題我們在效率提升、質量保障等方向的探索和實踐,在此將實踐過程中問題解決的思路和方案與大家一起分享,也希望能給大家帶來一些新的啟發 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...