利用 React 高階組件實現一個麵包屑導航

来源:https://www.cnblogs.com/zakum/archive/2020/07/03/13233296.html
-Advertisement-
Play Games

什麼是 React 高階組件 React 高階組件就是以高階函數的方式包裹需要修飾的 React 組件,並返回處理完成後的 React 組件。React 高階組件在 React 生態中使用的非常頻繁,比如react-router 中的 withRouter 以及 react-redux 中 conn ...


什麼是 React 高階組件

React 高階組件就是以高階函數的方式包裹需要修飾的 React 組件,並返回處理完成後的 React 組件。React 高階組件在 React 生態中使用的非常頻繁,比如react-router 中的 withRouter 以及 react-reduxconnect 等許多 API 都是以這樣的方式來實現的。

使用 React 高階組件的好處

在工作中,我們經常會有很多功能相似,組件代碼重覆的頁面需求,通常我們可以通過完全複製一遍代碼的方式實現功能,但是這樣頁面的維護可維護性就會變得極差,需要對每一個頁面里的相同組件去做更改。因此,我們可以將其中共同的部分,比如接受相同的查詢操作結果、組件外同一的標簽包裹等抽離出來,做一個單獨的函數,並傳入不同的業務組件作為子組件參數,而這個函數不會修改子組件,只是通過組合的方式將子組件包裝在容器組件中,是一個無副作用的純函數,從而我們能夠在不改變這些組件邏輯的情況下將這部分代碼解耦,提升代碼可維護性。

自己動手實現一個高階組件

前端項目里,帶鏈接指向的麵包屑導航十分常用,但由於麵包屑導航需要手動維護一個所有目錄路徑與目錄名映射的數組,而這裡所有的數據我們都能從 react-router 的路由表中取得,因此我們可以從這裡入手,實現一個麵包屑導航的高階組件。

首先我們看看我們的路由表提供的數據以及目標麵包屑組件所需要的數據:

// 這裡展示的是 react-router4 的route示例
let routes = [
  {
    breadcrumb: '一級目錄',
    path: '/a',
    component: require('../a/index.js').default,
    items: [
      {
        breadcrumb: '二級目錄',
        path: '/a/b',
        component: require('../a/b/index.js').default,
        items: [
          {
            breadcrumb: '三級目錄1',
            path: '/a/b/c1',
            component: require('../a/b/c1/index.js').default,
            exact: true,
          },
          {
            breadcrumb: '三級目錄2',
            path: '/a/b/c2',
            component: require('../a/b/c2/index.js').default,
            exact: true,
          },
      }
    ]
  }
]

// 理想中的麵包屑組件
// 展示格式為 a / b / c1 並都附上鏈接
const BreadcrumbsComponent = ({ breadcrumbs }) => (
  <div>
    {breadcrumbs.map((breadcrumb, index) => (
      <span key={breadcrumb.props.path}>
        <link to={breadcrumb.props.path}>{breadcrumb}</link>
        {index < breadcrumbs.length - 1 && <i> / </i>}
      </span>
    ))}
  </div>
);

這裡我們可以看到,麵包屑組件需要提供的數據一共有三種,一種是當前頁面的路徑,一種是麵包屑所帶的文字,一種是該麵包屑的導航鏈接指向。

其中第一種我們可以通過 react-router 提供的 withRouter 高階組件包裹,可使子組件獲取到當前頁面的 location 屬性,從而獲取頁面路徑。

後兩種需要我們對 routes 進行操作,首先將 routes 提供的數據扁平化成麵包屑導航需要的格式,我們可以使用一個函數來實現它。

/**
 * 以遞歸的方式展平react router數組
 */
const flattenRoutes = arr =>
  arr.reduce(function(prev, item) {
    prev.push(item);
    return prev.concat(
      Array.isArray(item.items) ? flattenRoutes(item.items) : item
    );
  }, []);

之後將展平的目錄路徑映射與當前頁面路徑一同放入處理函數,生成麵包屑導航結構。

export const getBreadcrumbs = ({ flattenRoutes, location }) => {
  // 初始化匹配數組match
  let matches = [];

  location.pathname
    // 取得路徑名,然後將路徑分割成每一路由部分.
    .split('?')[0]
    .split('/')
    // 對每一部分執行一次調用`getBreadcrumb()`的reduce.
    .reduce((prev, curSection) => {
      // 將最後一個路由部分與當前部分合併,比如當路徑為 `/x/xx/xxx` 時,pathSection分別檢查 `/x` `/x/xx` `/x/xx/xxx` 的匹配,並分別生成麵包屑
      const pathSection = `${prev}/${curSection}`;
      const breadcrumb = getBreadcrumb({
        flattenRoutes,
        curSection,
        pathSection,
      });

      // 將麵包屑導入到matches數組中
      matches.push(breadcrumb);

      // 傳遞給下一次reduce的路徑部分
      return pathSection;
    });
  return matches;
};

然後對於每一個麵包屑路徑部分,生成目錄名稱並附上指向對應路由位置的鏈接屬性。

const getBreadcrumb = ({ flattenRoutes, curSection, pathSection }) => {
  const matchRoute = flattenRoutes.find(ele => {
    const { breadcrumb, path } = ele;
    if (!breadcrumb || !path) {
      throw new Error(
        'Router中的每一個route必須包含 `path` 以及 `breadcrumb` 屬性'
      );
    }
    // 查找是否有匹配
    // exact 為 react router4 的屬性,用於精確匹配路由
    return matchPath(pathSection, { path, exact: true });
  });

  // 返回breadcrumb的值,沒有就返回原匹配子路徑名
  if (matchRoute) {
    return render({
      content: matchRoute.breadcrumb || curSection,
      path: matchRoute.path,
    });
  }

  // 對於routes表中不存在的路徑
  // 根目錄預設名稱為首頁.
  return render({
    content: pathSection === '/' ? '首頁' : curSection,
    path: pathSection,
  });
};

之後由 render 函數生成最後的單個麵包屑導航樣式。單個麵包屑組件需要為 render 函數提供該麵包屑指向的路徑 path, 以及該麵包屑內容映射content 這兩個 props。

/**
 *
 */
const render = ({ content, path }) => {
  const componentProps = { path };
  if (typeof content === 'function') {
    return <content {...componentProps} />;
  }
  return <span {...componentProps}>{content}</span>;
};

有了這些功能函數,我們就能實現一個能為包裹組件傳入當前所在路徑以及路由屬性的 React 高階組件了。傳入一個組件,返回一個新的相同的組件結構,這樣便不會對組件外的任何功能與操作造成破壞。

const BreadcrumbsHoc = (
  location = window.location,
  routes = []
) => Component => {
  const BreadComponent = (
    <Component
      breadcrumbs={getBreadcrumbs({
        flattenRoutes: flattenRoutes(routes),
        location,
      })}
    />
  );
  return BreadComponent;
};
export default BreadcrumbsHoc;

調用這個高階組件的方法也非常簡單,只需要傳入當前所在路徑以及整個 react router 生成的 routes 屬性即可。
至於如何取得當前所在路徑,我們可以利用 react router 提供的 withRouter 函數,如何使用請自行查閱相關文檔。
值得一提的是,withRouter 本身就是一個高階組件,能為包裹組件提供包括 location 屬性在內的若幹路由屬性。所以這個 API 也能作為學習高階組件一個很好的參考。

withRouter(({ location }) =>
  BreadcrumbsHoc(location, routes)(BreadcrumbsComponent)
);

4. Q&A

  1. 如果react router 生成的 routes 不是由自己手動維護的,甚至都沒有存在本地,而是通過請求拉取到的,存儲在 redux 里,通過 react-redux 提供的 connect 高階函數包裹時,路由發生變化時並不會導致該麵包屑組件更新。使用方法如下:
function mapStateToProps(state) {
  return {
    routes: state.routes,
  };
}

connect(mapStateToProps)(
  withRouter(({ location }) =>
    BreadcrumbsHoc(location, routes)(BreadcrumbsComponent)
  )
);

這其實是 connect 函數的一個bug。因為 react-redux 的 connect 高階組件會為傳入的參數組件實現 shouldComponentUpdate 這個鉤子函數,導致只有 prop 發生變化時才觸發更新相關的生命周期函數(含 render),而很顯然,我們的 location 對象並沒有作為 prop 傳入該參數組件。

官方推薦的做法是使用 withRouter 來包裹 connectreturn value,即

withRouter(
  connect(mapStateToProps)(({ location, routes }) =>
    BreadcrumbsHoc(location, routes)(BreadcrumbsComponent)
  )
);

其實我們從這裡也可以看出,高階組件同高階函數一樣,不會對組件的類型造成任何更改,因此高階組件就如同鏈式調用一樣,可以任意多層包裹來給組件傳入不同的屬性,在正常情況下也可以隨意調換位置,在使用上非常的靈活。這種可插拔特性使得高階組件非常受React生態的青睞,很多開源庫里都能看到這種特性的影子,有空也可以都拿出來分析一下。

本文由博客群發一文多發等運營工具平臺 OpenWrite 發佈


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

-Advertisement-
Play Games
更多相關文章
  • 原理: Redis集群採用一致性哈希槽的方式將集群中每個主節點都分配一定的哈希槽,對寫入的數據進行哈希後分配到某個主節點進行存儲。 集群使用公式(CRC16 key)& 16384計算鍵key數據那個槽。 16384個slot均勻分佈在各個節點上。 集群中每個主節點將承擔一部分槽點的維護,而槽點中存 ...
  • 準備4台虛擬機,安裝好ol7.7,分配固定ip192.168.168.11 12 13 14,其中192.168.168.11作為master,其他3個作為slave,主節點也同時作為namenode的同時也是datanode,192.168.168.14作為datanode的同時也作為second... ...
  • 本文更新於2019-06-22,使用MySQL 5.7,操作系統為Deepin 15.4。 為了便於描述,此處將創建視圖的DDL覆述一次,其已於“SQL”章節描述。 CREATE [OR REPLACE] [ALGORITHM={UNDEFINED|MERGE|TEMPTABLE}] VIEW vi ...
  • Plink是一個基於Flink的流處理平臺,旨在基於 [Apache Flink]封裝構建上層平臺。 提供常見的作業管理功能。如作業的創建,刪除,編輯,更新,保存,啟動,停止,重啟,管理,多作業模板配置等。 Flink SQL 編輯提交功能。如 SQL 的線上開發,智能提示,格式化,語法校驗,保存, ...
  • 前言 閑暇之時,羚羊給大家分享一下羚羊在Centos7 下安裝Cloudera Manager 6.3.0和cloudera cdh 6.3.2的過程和安裝過程中遇到的坑。至於為什麼要選擇CDH,Cloudera Manager和cdh是什麼,之間又是什麼關係,在這裡羚羊就不做介紹了。 為什麼選擇C ...
  • 零基礎入門貪吃蛇游戲 貪吃蛇是一款最常見、最經典、最受歡迎的小游戲之一。本篇文章帶你零基礎實現貪吃蛇游戲,一條蛇的使命從這裡開始。 演示地址:貪吃蛇演示,可能會提示危險操作,請忽略,放心訪問。 1、游戲描述 貪吃蛇是一款非常經典的休閑類游戲。在一塊固定大小的區域內,游戲玩家通過控制貪吃蛇的移動去吃食 ...
  • 前言: 在多線程中線程的執行順序是依靠哪個線程先獲得到CUP的執行權誰就先執行,雖然說可以通過線程的優先權進行設置,但是他只是獲取CUP執行權的概率高點,但是也不一定必須先執行。在這種情況下如何保證線程按照一定的順序進行執行,今天就來一個大總結,分別介紹一下幾種方式。 一、通過Object的wait ...
  • 如需轉載,請註明出處:Flutter學習筆記(16)--BottomNavigationBar底部item超過3個只顯示icon,不顯示title items: [ _bottomItem(Ids.home, 'ic_home_normal', 'ic_home_selected', 0), _bo ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...