国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

給 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

插件機制代碼

2010-01-13 13:53:32

VB.NET組件封裝

2021-09-16 14:22:06

微軟WinUI 2.7InfoBadge

2022-05-13 08:46:46

jsoneditorjson編輯器

2023-04-10 08:30:30

json編輯器typescript

2021-02-04 17:04:22

Python編程語言代碼

2021-06-08 11:31:11

WineWaylandVulkan

2021-04-30 17:35:16

前端開發技術熱點

2013-11-12 10:46:04

ChromeChrome32 be

2019-04-24 16:12:59

iOSSiriMacOS

2022-07-06 08:29:12

antdInput 組件

2021-02-05 07:03:17

微軟Edge瀏覽器

2022-01-25 10:34:37

微軟Edge Cana側邊欄

2020-11-20 10:52:54

Antd表格日程

2021-10-07 09:03:44

Uniapp封裝組件

2010-07-28 10:29:03

Flex開源
點贊
收藏

51CTO技術棧公眾號

成人久久电影| 91麻豆蜜桃一区二区三区| 久久天天躁日日躁| 色综合久久影院| 五月婷婷综合激情| 米奇.777.com| 亚洲人成精品久久久久久| 97超碰青青草| 99视频精品在线| 糖心vlog在线免费观看| 欧美精品免费看| 性生大片免费观看性| 92国产精品观看| 老司机激情视频| 九九久久精品视频| 亚洲国产精品久久久久婷婷老年 | 国产欧美黑人| 色综合天天综合网国产成人综合天 | 日本高清不卡一区二区三区视频 | 国产a级毛片一区| 欧美黄色免费网址| 成人性生交大片免费看中文网站| 这里只有精品66| 国产一区二区久久| 国产免费黄色小视频| 99这里只有精品| 亚洲天堂2018av| 亚洲激情av在线| 日本中文字幕一区二区有码在线| 国产福利电影在线播放| 欧美午夜精品理论片a级大开眼界 欧美午夜精品久久久久免费视 | 欧美系列一区二区| av在线电影观看| 精品欧美一区二区三区精品久久| 深夜国产在线播放| 亚洲欧美日韩一区二区三区在线| 欧美成人一二区| 久久久久久成人精品| 日韩精品二区| 久久精品一区二区三区不卡免费视频| 秋霞影院一区二区| 欧美日本视频在线观看| 亚洲欧美日韩久久| 波多野结衣在线影院| 亚洲电影免费观看高清完整版在线观看| 中文字幕在线免费观看视频| 欧美激情一区二区久久久| 中文字幕亚洲综合久久五月天色无吗''| 欧洲亚洲一区| 中文字幕高清一区| 黄网在线观看| 一本一本久久a久久精品综合小说| 视频二区欧美| 91pron在线| 国产成人av影院| 性色a∨人人爽网站| 亚洲国产福利在线| 欧美国产极品| 日韩免费电影一区二区| 欧美国产精品劲爆| 97视频在线观看网站| 最近2019中文字幕mv免费看| 99精品美女| 欧美精品一区免费| 欧美日本在线视频| 精品福利一区| 日本丰满少妇黄大片在线观看| 亚洲久草在线视频| 成人国产二区| 亚洲综合在线播放| 337p粉嫩大胆色噜噜噜噜亚洲| 国产乱子伦三级在线播放| 久久高清视频免费| 爽好多水快深点欧美视频| 丝袜国产免费观看| 色系列之999| 欧美三级午夜理伦三级中文幕| 久久99久久99精品| 色偷偷一区二区三区| 电影一区中文字幕| 久久久com| 日韩理论片中文av| 二区三区不卡| 黑人中文字幕一区二区三区| 综合欧美亚洲日本| 成人做爰免费视频免费看| 精品网站在线看| 亚洲图片欧美一区| 精品国产18久久久久久二百| 欧美精品亚洲精品| 亚洲一区二区三区美女| 国产精一区二区| 午夜精品视频在线观看一区二区| 五月天激情综合| 操欧美女人视频| 青青草国产免费| 精品国产免费人成在线观看| 国内激情久久| 中文在线三区| 国产va免费精品高清在线观看 | 亚洲三级在线看| 91tv亚洲精品香蕉国产一区| 久久99精品久久久久久水蜜桃| 国产女同互慰高潮91漫画| 亚洲永久av| 亚洲成色www久久网站| 精品视频1区2区3区| 97欧美在线视频| 性视频在线播放| 欧美自拍视频在线| 国产午夜一区二区三区| 欧美性aaa| 少妇高潮喷水在线观看| 亚洲欧美制服第一页| 日韩成人免费电影| 麻豆网站在线观看| 国产日韩久久| 欧美在线观看视频一区二区| 成人影视亚洲图片在线| 97cao在线| 国内偷自视频区视频综合| 久久久亚洲午夜电影| 99久热在线精品视频观看| 黄色一级在线视频| 欧美性久久久| 韩国精品久久久999| 久久国产三级精品| 91最新在线| 91免费看蜜桃| 欧美日韩综合视频| 911精品美国片911久久久| 最新天堂资源在线资源| 国产精品美女午夜av| 一区二区在线观看免费视频播放| 欧美综合自拍| 国产剧情演绎av| 国产成人亚洲综合91精品| 一区二区三区日韩| 91偷拍一区二区三区精品| 欧美一区二区少妇| 99国产精品久久久久老师| 91成人在线观看喷潮| 一区二区三区福利| av2020不卡| 免费看国产一级片| 97久久超碰福利国产精品…| 亚洲欧美日韩系列| 91成人精品视频| www免费在线观看| 特级毛片在线免费观看| 国产亚洲欧洲在线| 欧美国产一区在线| 国产亚洲电影| 一二三区在线视频| 久久久久久久久久久久久久久久av| 日韩精品一区二区三区四区视频 | 成人精品福利| 日产精品一线二线三线芒果| 日韩激情在线视频| 久久综合色天天久久综合图片| 欧美在线导航| 国产在线视频你懂得| 亚洲日本无吗高清不卡| 久久在线观看视频| 午夜久久电影网| 国产视频久久| 亚洲欧洲二区| 中文视频在线| 艳色歌舞团一区二区三区| 久久国产精品免费视频| 精品久久久久久中文字幕一区奶水| 久久天堂精品| 91欧美日韩在线| av基地在线| 日韩av在线综合| av成人综合网| 色偷偷亚洲男人天堂| 调教+趴+乳夹+国产+精品| 蜜臀av一区二区在线观看| 北条麻妃一区二区三区在线| 在线观看免费网站黄| 国产极品粉嫩福利姬萌白酱| 亚洲一区二区三| 色哟哟网站入口亚洲精品| 黑丝美女久久久| 成人午夜在线播放| 欧美777四色影| 日韩成人一区| 婷婷成人激情| 人人爽人人av| 裸模一区二区三区免费| 欧美激情第一页xxx| 欧美一区二区视频在线观看2020| 国产人久久人人人人爽| 丝袜美腿一区二区三区| 久久99国内| 成人免费在线观看视频| 91在线看黄| 日本中文字幕视频| 熟女少妇在线视频播放|