成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

給 Antd Table 組件編寫縮進指引線、子節點懶加載等功能,如何二次封裝開源組件?

開源
在業務需求中,有時候我們需要基于 antd 之類的組件庫定制很多功能,本文就以我自己遇到的業務需求為例,一步步實現和優化一個樹狀表格組件。

[[384776]]

在業務需求中,有時候我們需要基于 antd 之類的組件庫定制很多功能,本文就以我自己遇到的業務需求為例,一步步實現和優化一個樹狀表格組件,這個組件會支持:

  • 每個層級縮進指示線
  • 遠程懶加載子節點
  • 每個層級支持分頁

本系列分為兩篇文章,這篇只是講這些業務需求如何實現。

而下一篇,我會講解怎么給組件也設計一套簡單的插件機制,來解決代碼耦合,難以維護的問題。

功能實現

層級縮進線

antd 的 Table 組件默認是沒有提供這個功能的,它只是支持了樹狀結構:

  1. const treeData = [ 
  2.   { 
  3.     function_name: `React Tree Reconciliation`, 
  4.     count: 100, 
  5.     children: [ 
  6.       { 
  7.         function_name: `React Tree Reconciliation2`, 
  8.         count: 100 
  9.       } 
  10.     ] 
  11.   } 

展示效果如下:

antd-table

 

可以看出,在展示大量的函數堆棧的時候,沒有縮進線就會很難受了,業務方也確實和我提過這個需求,可惜之前太忙了,就暫時放一邊了。😁

參考 VSCode 中的縮進線效果,可以發現,縮進線是和節點的層級緊密相關的。

vscode

 

比如 src 目錄對應的是第一級,那么它的子級 client 和 node 就只需要在 td 前面繪制一條垂直線,而 node 下的三個目錄則繪制兩條垂直線。

  1. 第 1 層: | text 
  2. 第 2 層: | | text 
  3. 第 3 層: | | | text 

只需要在自定義渲染單元格元素的時候,得到以下兩個信息。

  1. 當前節點的層級信息。
  2. 當前節點的父節點是否是展開狀態。

所以思路就是對數據進行一次遞歸處理,把層級寫在節點上,并且要把父節點的引用也寫上,之后再通過傳給 Table 的 expandedRowKeys 屬性來維護表格的展開行數據。

這里我是直接改寫了原始數據,如果需要保證原始數據干凈的話,也可以參考 React Fiber 的思路,構建一顆替身樹進行數據寫入,只要保留原始樹節點的引用即可。

  1. /** 
  2.  * 遞歸樹的通用函數 
  3.  */ 
  4. const traverseTree = ( 
  5.   treeList, 
  6.   childrenColumnName, 
  7.   callback 
  8. ) => { 
  9.   const traverse = (list, parent = nulllevel = 1) => { 
  10.     list.forEach(treeNode => { 
  11.       callback(treeNode, parent, level); 
  12.       const { [childrenColumnName]: next } = treeNode; 
  13.       if (Array.isArray(next)) { 
  14.         traverse(next, treeNode, level + 1); 
  15.       } 
  16.     }); 
  17.   }; 
  18.   traverse(treeList); 
  19. }; 
  20.  
  21. function rewriteTree({ dataSource }) { 
  22.   traverseTree(dataSource, childrenColumnName, (node, parent, level) => { 
  23.     // 記錄節點的層級 
  24.     node[INTERNAL_LEVEL] = level 
  25.     // 記錄節點的父節點 
  26.     node[INTERNAL_PARENT] = parent 
  27.   }) 

之后利用 Table 組件提供的 components 屬性,自定義渲染 Cell 組件,也就是 td 元素。

  1. const components = { 
  2.   body: { 
  3.     cell: (cellProps) => ( 
  4.       <TreeTableCell 
  5.         {...props} 
  6.         {...cellProps} 
  7.         expandedRowKeys={expandedRowKeys} 
  8.       /> 
  9.     ) 
  10.   } 

之后,在自定義渲染的 Cell 中,只需要獲取兩個信息,只需要根據層級和父節點的展開狀態,來決定繪制幾條垂直線即可。

  1. const isParentExpanded = expandedRowKeys.includes( 
  2.   record?.[INTERNAL_PARENT]?.[rowKey] 
  3. // 只有當前是展示指引線的列 且父節點是展開節點 才會展示縮進指引線 
  4. if (dataIndex !== indentLineDataIndex || !isParentExpanded) { 
  5.   return <td className={className}>{children}</td> 
  6.  
  7. // 只要知道層級 就知道要在 td 中繪制幾條垂直指引線 舉例來說: 
  8. // 第 2 層: | | text 
  9. // 第 3 層: | | | text 
  10. const level = record[INTERNAL_LEVEL] 
  11.  
  12. const indentLines = renderIndentLines(level

這里的實現就不再贅述,直接通過絕對定位畫幾條垂直線,再通過對 level 進行循環時的下標 index 決定 left 的偏移值即可。

效果如圖所示:

縮進線

 

遠程懶加載子節點

這個需求就需要用比較 hack 的手段實現了,首先觀察了一下 Table 組件的邏輯,只有在有children 的子節點上才會展示「展開更多」的圖標。

所以思路就是,和后端約定一個字段比如 has_next,之后預處理數據的時候先遍歷這些節點,加上一個假的占位 children。

之后在點擊展開的時候,把節點上的這個假 children 刪除掉,并且把通過改寫節點上一個特殊的 is_loading 字段,在自定義渲染 Icon 的代碼中判斷,并且展示 Loading Icon。

又來到遞歸樹的邏輯中,我們加入這樣的一段代碼:

  1. function rewriteTree({ dataSource }) { 
  2.   traverseTree(dataSource, childrenColumnName, (node, parent, level) => { 
  3.     if (node[hasNextKey]) { 
  4.       // 樹表格組件要求 next 必須是非空數組才會渲染「展開按鈕」 
  5.       // 所以這里手動添加一個占位節點數組 
  6.       // 后續在 onExpand 的時候再加載更多節點 并且替換這個數組 
  7.       node[childrenColumnName] = [generateInternalLoadingNode(rowKey)] 
  8.     } 
  9.   }) 

之后我們要實現一個 forceUpdate 函數,驅動組件強制渲染:

  1. const [_, forceUpdate] = useReducer((x) => x + 1, 0) 

再來到 onExpand 的邏輯中:

  1. const onExpand = async (expanded, record) => { 
  2.   if (expanded && record[hasNextKey] && onLoadMore) { 
  3.     // 標識節點的 loading 
  4.     record[INTERNAL_IS_LOADING] = true 
  5.     // 移除用來展示展開箭頭的假 children 
  6.     record[childrenColumnName] = null 
  7.     forceUpdate() 
  8.     const childList = await onLoadMore(record) 
  9.     record[hasNextKey] = false 
  10.     addChildList(record, childList) 
  11.   } 
  12.   onExpandProp?.(expanded, record) 
  13.  
  14. function addChildList(record, childList) { 
  15.   record[childrenColumnName] = childList 
  16.   record[INTERNAL_IS_LOADING] = false 
  17.   rewriteTree({ 
  18.     dataSource: childList, 
  19.     parentNode: record 
  20.   }) 
  21.   forceUpdate() 

這里 onLoadMore 是用戶傳入的獲取更多子節點的方法,

流程是這樣的:

  1. 節點展開時,先給節點寫入一個正在加載的標志,然后把子數據重置為空。這樣雖然節點會變成展開狀態,但是不會渲染子節點,然后強制渲染。
  2. 在加載完成后賦值了新的子節點 record[childrenColumnName] = childList 后,我們又通過 forceUpdate 去強制組件重渲染,展示出新的子節點。

需要注意,我們遞歸樹加入邏輯的所有邏輯都在 rewriteTree 中,所以對于加入的新的子節點,也需要通過這個函數遞歸一遍,加入 level, parent 等信息。

新加入的節點的 level 需要根據父節點的 level 相加得出,不能從 1 開始,否則渲染的縮進線就亂掉了,所以這個函數需要改寫,加入 parentNode 父節點參數,遍歷時寫入的 level 都要加上父節點已有的 level。

  1. function rewriteTree({ 
  2.   dataSource, 
  3.   // 在動態追加子樹節點的時候 需要手動傳入 parent 引用 
  4.   parentNode = null 
  5. }) { 
  6.   // 在動態追加子樹節點的時候 需要手動傳入父節點的 level 否則 level 會從 1 開始計算 
  7.   const startLevel = parentNode?.[INTERNAL_LEVEL] || 0 
  8.  
  9.   traverseTree(dataSource, childrenColumnName, (node, parent, level) => { 
  10.       parent = parent || parentNode; 
  11.       // 記錄節點的層級 
  12.       node[INTERNAL_LEVEL] = level + startLevel; 
  13.       // 記錄節點的父節點 
  14.       node[INTERNAL_PARENT] = parent; 
  15.  
  16.     if (node[hasNextKey]) { 
  17.       // 樹表格組件要求 next 必須是非空數組才會渲染「展開按鈕」 
  18.       // 所以這里手動添加一個占位節點數組 
  19.       // 后續在 onExpand 的時候再加載更多節點 并且替換這個數組 
  20.       node[childrenColumnName] = [generateInternalLoadingNode(rowKey)] 
  21.     } 
  22.   }) 

自定義渲染 Loading Icon 就很簡單了:

  1. // 傳入給 Table 組件的 expandIcon 屬性即可 
  2. export const TreeTableExpandIcon = ({ 
  3.   expanded, 
  4.   expandable, 
  5.   onExpand, 
  6.   record 
  7. }) => { 
  8.   if (record[INTERNAL_IS_LOADING]) { 
  9.     return <IconLoading style={iconStyle} /> 
  10.   } 

功能完成,看一下效果:

遠程懶加載

 

每個層級支持分頁

這個功能和上一個功能也有點類似,需要在 rewriteTree 的時候根據外部傳入的是否開啟分頁的字段,在符合條件的時候往子節點數組的末尾加入一個占位 Pagination 節點。

之后在 column 的 render 中改寫這個節點的渲染邏輯。

改寫 record:

  1. function rewriteTree({ 
  2.   dataSource, 
  3.   // 在動態追加子樹節點的時候 需要手動傳入 parent 引用 
  4.   parentNode = null 
  5. }) { 
  6.   // 在動態追加子樹節點的時候 需要手動傳入父節點的 level 否則 level 會從 1 開始計算 
  7.   const startLevel = parentNode?.[INTERNAL_LEVEL] || 0 
  8.  
  9.   traverseTree(dataSource, childrenColumnName, (node, parent, level) => { 
  10.     // 加載更多邏輯 
  11.     if (node[hasNextKey]) { 
  12.       // 樹表格組件要求 next 必須是非空數組才會渲染「展開按鈕」 
  13.       // 所以這里手動添加一個占位節點數組 
  14.       // 后續在 onExpand 的時候再加載更多節點 并且替換這個數組 
  15.       node[childrenColumnName] = [generateInternalLoadingNode(rowKey)] 
  16.     } 
  17.  
  18.     // 分頁邏輯 
  19.     if (childrenPagination) { 
  20.       const { totalKey } = childrenPagination; 
  21.       const nodeChildren = node[childrenColumnName] || []; 
  22.       const [lastChildNode] = nodeChildren.slice?.(-1); 
  23.       // 渲染分頁器,先加入占位節點 
  24.       if ( 
  25.         node[totalKey] > nodeChildren?.length && 
  26.         // 防止重復添加分頁器占位符 
  27.         !isInternalPaginationNode(lastChildNode, rowKey) 
  28.       ) { 
  29.         nodeChildren?.push?.(generateInternalPaginationNode(rowKey)); 
  30.       } 
  31.     } 
  32.   }) 

改寫 columns:

  1. function rewriteColumns() { 
  2.   /** 
  3.    * 根據占位符 渲染分頁組件 
  4.    */ 
  5.   const rewritePaginationRender = (column) => { 
  6.     column.render = function ColumnRender(text, record) { 
  7.       if ( 
  8.         isInternalPaginationNode(record, rowKey) && 
  9.         dataIndex === indentLineDataIndex 
  10.       ) { 
  11.         return <Pagination /> 
  12.       } 
  13.       return render?.(text, record) ?? text 
  14.     } 
  15.   } 
  16.  
  17.   columns.forEach((column) => { 
  18.     rewritePaginationRender(column
  19.   }) 

來看一下實現的分頁效果:

 

重構和優化

隨著編寫功能的增多,邏輯被耦合在 Antd Table 的各個回調函數之中,

  • 指引線的邏輯分散在 rewriteColumns, components中。
  • 分頁的邏輯被分散在 rewriteColumns 和 rewriteTree 中。
  • 加載更多的邏輯被分散在 rewriteTree 和 onExpand 中

至此,組件的代碼行數也已經來到了 300 行,大概看一下代碼的結構,已經是比較混亂了:

  1. export const TreeTable = (rawProps) => { 
  2.   function rewriteTree() { 
  3.     // 🎈加載更多邏輯 
  4.     // 🔖 分頁邏輯 
  5.   } 
  6.  
  7.   function rewriteColumns() { 
  8.     // 🔖 分頁邏輯 
  9.     // 🏁 縮進線邏輯 
  10.   } 
  11.  
  12.   const components = { 
  13.     // 🏁 縮進線邏輯 
  14.   } 
  15.  
  16.   const onExpand = async (expanded, record) => { 
  17.     // 🎈 加載更多邏輯 
  18.   } 
  19.  
  20.   return <Table /> 

 

有沒有一種機制,可以讓代碼按照功能點聚合,而不是散落在各個函數中?

  1. // 🔖 分頁邏輯 
  2. const usePaginationPlugin = () => {} 
  3. // 🎈 加載更多邏輯 
  4. const useLazyloadPlugin = () => {} 
  5. // 🏁 縮進線邏輯 
  6. const useIndentLinePlugin = () => {} 
  7.  
  8. export const TreeTable = (rawProps) => { 
  9.   usePaginationPlugin() 
  10.  
  11.   useLazyloadPlugin() 
  12.  
  13.   useIndentLinePlugin() 
  14.  
  15.   return <Table /> 

沒錯,就是很像 VueCompositionAPI 和 React Hook 在邏輯解耦方面所做的改進,但是在這個回調函數的寫法形態下,好像不太容易做到?

下一篇文章,我會聊聊如何利用自己設計的插件機制來優化這個組件的耦合代碼。

記得關注后加我好友,我會不定期分享前端知識,行業信息。2021 陪你一起度過。

 本文轉載自微信公眾號「前端從進階到入院」,可以通過以下二維碼關注。轉載本文請聯系前端從進階到入院公眾號。

 

責任編輯:武曉燕 來源: 前端從進階到入院
相關推薦

2022-10-17 08:03:47

封裝vue組件

2024-03-20 09:31:00

圖片懶加載性能優化React

2017-03-28 10:11:12

Webpack 2React加載

2021-11-22 10:00:33

鴻蒙HarmonyOS應用

2021-03-04 08:19:29

插件機制代碼

2021-09-16 14:22:06

微軟WinUI 2.7InfoBadge

2010-01-13 13:53:32

VB.NET組件封裝

2022-05-13 08:46:46

jsoneditorjson編輯器

2023-04-10 08:30:30

json編輯器typescript

2013-11-12 10:46:04

ChromeChrome32 be

2019-04-24 16:12:59

iOSSiriMacOS

2021-06-08 11:31:11

WineWaylandVulkan

2021-02-04 17:04:22

Python編程語言代碼

2021-04-30 17:35:16

前端開發技術熱點

2022-07-06 08:29:12

antdInput 組件

2022-01-25 10:34:37

微軟Edge Cana側邊欄

2021-02-05 07:03:17

微軟Edge瀏覽器

2020-11-20 10:52:54

Antd表格日程

2021-10-07 09:03:44

Uniapp封裝組件

2010-07-28 10:29:03

Flex開源
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 午夜99| 视频三区| 亚洲精品久久久久久久不卡四虎 | 久草欧美| 亚洲精品国产综合区久久久久久久 | 成年人在线观看 | m豆传媒在线链接观看 | 青青草原综合久久大伊人精品 | 亚洲男女激情 | 久久亚洲春色中文字幕久久久 | 日韩视频在线一区二区 | 日韩高清国产一区在线 | 国产成人精品免费视频大全最热 | aaa一区| 精品日本久久久久久久久久 | 日韩av美女电影 | 欧美成人激情 | 秋霞a级毛片在线看 | 日韩视频一区二区 | 久久久久网站 | 成人在线视频网站 | 国产黄色在线 | 成人网在线观看 | 国精日本亚洲欧州国产中文久久 | 一区二区日韩 | 久久久久久久av麻豆果冻 | 91免费在线 | 中文字幕第一页在线 | 国产精品明星裸体写真集 | 午夜视频在线免费观看 | 国产亚洲精品美女久久久久久久久久 | 7799精品视频天天看 | 中文字幕一区二区三区在线观看 | 精品视频一区二区三区在线观看 | 午夜免费电影院 | 日日操av | 中文字幕不卡在线观看 | 欧洲色综合 | 日韩成人在线观看 | 欧美在线高清 | www九色|