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

如何編寫神奇的「插件機制」,優化基于 Antd Table 封裝表格的混亂代碼

開發 前端
以及開發過程中出現代碼的耦合,難以維護問題,進而延伸探索插件機制在組件中的設計和使用,雖然本文設計的插件還是最簡陋的版本,但是原理大致上如此,希望能夠對你有所啟發。

[[385120]]

前言

最近在一個業務需求中,我通過在 Antd Table 提供的回調函數等機制中編寫代碼,實現了這些功能:

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

最后實現的效果大概是這樣的:

最終效果

這篇文章我想聊聊我在這個需求中,對代碼解耦,為組件編寫插件機制的一些思考。

重構思路

隨著編寫功能的增多,邏輯被耦合在 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 />; 
  21. }; 

這時候缺點就暴露出來了,當我想要改動或者刪減其中一個功能的時候變得異常痛苦,經常在各個函數之間跳轉查找。

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

  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 />; 
  16. }; 

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

這時候,我回想到社區中一些開源框架提供的插件機制,好像就可以在不深入源碼的情況下注入各個回調時機的用戶邏輯。

比如 Vite 的插件[1]、Webpack 的插件[2] 甚至大家很熟悉的 Vue.use()[3],它們本質上就是對外暴露出一些內部的時機和屬性,讓用戶去寫一些代碼來介入框架運行的各個時機之中。

那么,我們是否可以考慮把「處理每個節點、column、每次 onExpand」 的時機暴露出去,這樣讓用戶也可以介入這些流程,去改寫一些屬性,調用一些內部方法,以此實現上面的幾個功能呢?

我們設計插件機制,想要實現這兩個目標:

  1. 邏輯解耦,把每個小功能的代碼整合到插件文件中去,不和組件耦合起來,增加可維護性。
  2. 用戶共建,內部使用的話方便同事共建,開源后方便社區共建,當然這要求你編寫的插件機制足夠完善,文檔足夠友好。

不過插件也會帶來一些缺點,設計一套完善的插件機制也是非常復雜的,像 Webpack、Rollup、Redux 的插件機制都有設計的非常精良的地方可以參考學習。

接下來,我會試著實現的一個最簡化版的插件系統。

源碼

首先,設計一下插件的接口:

  1. export interface TreeTablePlugin<T = any> { 
  2.   (props: ResolvedProps, context: TreeTablePluginContext): { 
  3.     /** 
  4.      * 可以訪問到每一個 column 并修改 
  5.      */ 
  6.     onColumn?(column: ColumnProps<T>): void; 
  7.     /** 
  8.      * 可以訪問到每一個節點數據 
  9.      * 在初始化或者新增子節點以后都會執行 
  10.      */ 
  11.     onRecord?(record): void; 
  12.     /** 
  13.      * 節點展開的回調函數 
  14.      */ 
  15.     onExpand?(expanded, record): void; 
  16.     /** 
  17.      * 自定義 Table 組件 
  18.      */ 
  19.     components?: TableProps<T>['components']; 
  20.   }; 
  21.  
  22. export interface TreeTablePluginContext { 
  23.   forceUpdate: React.DispatchWithoutAction; 
  24.   replaceChildList(record, childList): void; 
  25.   expandedRowKeys: TableProps<any>['expandedRowKeys']; 
  26.   setExpandedRowKeys: (v: string[] | number[] | undefined) => void; 

我把插件設計成一個函數,這樣每次執行都可以拿到最新的 props 和 context。

context 其實就是組件內一些依賴上下文的工具函數等等,比如 forceUpdate, replaceChildList 等函數都可以掛在上面。

接下來,由于插件可能有多個,而且內部可能會有一些解析流程,所以我設計一個運行插件的 hook 函數 usePluginContainer:

  1. export const usePluginContainer = ( 
  2.   props: ResolvedProps, 
  3.   context: TreeTablePluginContext 
  4. ) => { 
  5.   const { plugins: rawPlugins } = props; 
  6.  
  7.   const plugins = rawPlugins.map(usePlugin => usePlugin?.(props, context)); 
  8.  
  9.   const container = { 
  10.     onColumn(column: ColumnProps<any>) { 
  11.       for (const plugin of plugins) { 
  12.         plugin?.onColumn?.(column); 
  13.       } 
  14.     }, 
  15.     onRecord(record, parentRecord, level) { 
  16.       for (const plugin of plugins) { 
  17.         plugin?.onRecord?.(record, parentRecord, level); 
  18.       } 
  19.     }, 
  20.     onExpand(expanded, record) { 
  21.       for (const plugin of plugins) { 
  22.         plugin?.onExpand?.(expanded, record); 
  23.       } 
  24.     }, 
  25.     /** 
  26.      * 暫時只做 components 的 deepmerge 
  27.      * 不處理自定義組件的沖突 后定義的 Cell 會覆蓋前者 
  28.      */ 
  29.     mergeComponents() { 
  30.       let components: TableProps<any>['components'] = {}; 
  31.       for (const plugin of plugins) { 
  32.         components = deepmerge.all([ 
  33.           components, 
  34.           plugin.components || {}, 
  35.           props.components || {}, 
  36.         ]); 
  37.       } 
  38.       return components; 
  39.     }, 
  40.   }; 
  41.  
  42.   return container; 
  43. }; 

目前的流程很簡單,只是把每個 plugin 函數調用一下,然后提供對外的包裝接口。mergeComponent 使用deepmerge[4] 這個庫來合并用戶傳入的 components 和 插件中的 components,暫時不做沖突處理。

接著就可以在組件中調用這個函數,生成 pluginContainer:

  1. export const TreeTable =  React.forwardRef((props, ref) => { 
  2.   const [_, forceUpdate] = useReducer((x) => x + 1, 0) 
  3.  
  4.   const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]) 
  5.  
  6.   const pluginContext = { 
  7.     forceUpdate, 
  8.     replaceChildList, 
  9.     expandedRowKeys, 
  10.     setExpandedRowKeys 
  11.   } 
  12.  
  13.   // 對外暴露工具方法給用戶使用 
  14.   useImperativeHandle(ref, () => ({ 
  15.     replaceChildList, 
  16.     setNodeLoading, 
  17.   })); 
  18.  
  19.   // 這里拿到了 pluginContainer 
  20.   const pluginContainer = usePluginContainer( 
  21.     { 
  22.       ...props, 
  23.       plugins: [usePaginationPlugin, useLazyloadPlugin, useIndentLinePlugin], 
  24.     }, 
  25.     pluginContext 
  26.   ); 
  27.  
  28. }) 

之后,在各個流程的相應位置,都通過 pluginContainer 執行相應的鉤子函數即可:

  1. export const TreeTable = React.forwardRef((props, ref) => { 
  2.   // 省略上一部分代碼…… 
  3.  
  4.   // 這里拿到了 pluginContainer 
  5.   const pluginContainer = usePluginContainer( 
  6.     { 
  7.       ...props, 
  8.       plugins: [usePaginationPlugin, useLazyloadPlugin, useIndentLinePlugin], 
  9.     }, 
  10.     pluginContext 
  11.   ); 
  12.  
  13.   // 遞歸遍歷整個數據 調用鉤子 
  14.   const rewriteTree = ({ 
  15.     dataSource, 
  16.     // 在動態追加子樹節點的時候 需要手動傳入 parent 引用 
  17.     parentNode = null
  18.   }) => { 
  19.     pluginContainer.onRecord(parentNode); 
  20.  
  21.     traverseTree(dataSource, childrenColumnName, (node, parent, level) => { 
  22.       // 這里執行插件的 onRecord 鉤子 
  23.       pluginContainer.onRecord(node, parent, level); 
  24.     }); 
  25.   } 
  26.  
  27.   const rewrittenColumns = columns.map(rawColumn => { 
  28.     //  這里把淺拷貝過后的 column 暴露出去 
  29.     //  防止污染原始值 
  30.     const column = Object.assign({}, rawColumn); 
  31.     pluginContainer.onColumn(column); 
  32.     return column
  33.   }); 
  34.  
  35.   const onExpand = async (expanded, record) => { 
  36.     // 這里執行插件的 onExpand 鉤子 
  37.     pluginContainer.onExpand(expanded, record); 
  38.   }; 
  39.  
  40.   // 這里獲取合并后的 components 傳遞給 Table 
  41.   const components = pluginContainer.mergeComponents() 
  42. }); 

之后,我們就可以把之前分頁相關的邏輯直接抽象成 usePaginationPlugin:

  1. export const usePaginationPlugin: TreeTablePlugin = ( 
  2.   props: ResolvedProps, 
  3.   context: TreeTablePluginContext 
  4. ) => { 
  5.   const { forceUpdate, replaceChildList } = context; 
  6.   const { 
  7.     childrenPagination, 
  8.     childrenColumnName, 
  9.     rowKey, 
  10.     indentLineDataIndex, 
  11.   } = props; 
  12.  
  13.   const handlePagination = node => { 
  14.     // 先加入渲染分頁器占位節點 
  15.   }; 
  16.  
  17.   const rewritePaginationRender = column => { 
  18.     // 改寫 column 的 render 
  19.     // 渲染分頁器 
  20.   }; 
  21.  
  22.   return { 
  23.     onRecord: handlePagination, 
  24.     onColumn: rewritePaginationRender, 
  25.   }; 
  26. }; 

也許機智的你已經發現,這里的插件是以 use 開頭的,這是自定義 hook的標志。

沒錯,它既是一個插件,同時也是一個 自定義 Hook。所以你可以使用 React Hook 的一切能力,同時也可以在插件中引入各種社區的第三方 Hook 來加強能力。

這是因為我們是在 usePluginContainer 中通過函數調用執行各個 usePlugin,完全符合 React Hook 的調用規則。

而懶加載節點相關的邏輯也可以抽象成 useLazyloadPlugin:

  1. export const useLazyloadPlugin: TreeTablePlugin = ( 
  2.   props: ResolvedProps, 
  3.   context: TreeTablePluginContext 
  4. ) => { 
  5.   const { childrenColumnName, rowKey, hasNextKey, onLoadMore } = props; 
  6.   const { replaceChildList, expandedRowKeys, setExpandedRowKeys } = context; 
  7.  
  8.   // 處理懶加載占位節點邏輯 
  9.   const handleNextLevelLoader = node => {}; 
  10.  
  11.   const onExpand = async (expanded, record) => { 
  12.     if (expanded && record[hasNextKey] && onLoadMore) { 
  13.       // 處理懶加載邏輯 
  14.     } 
  15.   }; 
  16.  
  17.   return { 
  18.     onRecord: handleNextLevelLoader, 
  19.     onExpand: onExpand, 
  20.   }; 
  21. }; 

而縮進線相關的邏輯則抽取成 useIndentLinePlugin:

  1. export const useIndentLinePlugin: TreeTablePlugin = ( 
  2.   props: ResolvedProps, 
  3.   context: TreeTablePluginContext 
  4. ) => { 
  5.   const { expandedRowKeys } = context; 
  6.   const onColumn = column => { 
  7.     column.onCell = record => { 
  8.       return { 
  9.         record, 
  10.         ...column
  11.       }; 
  12.     }; 
  13.   }; 
  14.  
  15.   const components = { 
  16.     body: { 
  17.       cell: cellProps => ( 
  18.         <IndentCell 
  19.           {...props} 
  20.           {...cellProps} 
  21.           expandedRowKeys={expandedRowKeys} 
  22.         /> 
  23.       ), 
  24.     }, 
  25.   }; 
  26.  
  27.   return { 
  28.     components, 
  29.     onColumn, 
  30.   }; 
  31. }; 

至此,主函數被精簡到 150 行左右,新功能相關的函數全部被移到插件目錄中去了,無論是想要新增或者刪減、開關功能都變的非常容易。

此時的目錄結構:

目錄結構

總結

本系列通過講述擴展 Table 組件的如下功能:

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

以及開發過程中出現代碼的耦合,難以維護問題,進而延伸探索插件機制在組件中的設計和使用,雖然本文設計的插件還是最簡陋的版本,但是原理大致上如此,希望能夠對你有所啟發。

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

 

責任編輯:武曉燕
相關推薦

2011-06-09 17:26:17

Qt 插件 API

2021-06-22 06:52:46

Vite 插件機制Rollup

2023-11-07 10:19:08

2009-12-11 10:29:03

PHP插件機制

2010-09-08 14:39:35

2011-01-21 15:02:14

jQuerywebJavaScript

2023-06-15 08:01:01

Vite插件機制

2024-02-23 08:00:00

2024-07-17 09:23:58

Vite插件機制

2020-06-23 07:50:13

Python開發技術

2022-06-07 08:59:58

hookuseRequestReact 項目

2021-12-19 07:21:48

Webpack 前端插件機制

2020-05-22 09:10:10

前端框架插件

2021-03-03 08:32:09

開源子節點組件

2012-07-11 10:51:37

編程

2011-03-28 11:20:11

Nagios 插件

2011-04-06 16:02:26

Nagios插件

2022-06-07 09:30:35

JavaScript變量名參數

2024-06-24 14:19:48

2021-03-17 08:00:59

JS語言Javascript
點贊
收藏

51CTO技術棧公眾號

亚洲婷婷综合久久一本伊一区| 久久国产免费| 欧美日韩久久久久久| 亚洲中文字幕无码中文字| 欧美日韩精品| 成人a免费视频| 日韩中文字幕视频网| 精品国产一区二区三区久久久蜜月 | 欧美一区二区在线视频| 青柠在线影院观看日本| 91麻豆国产在线观看| 人妻互换免费中文字幕| 精品999日本| 91影院未满十八岁禁止入内| 亚洲精品无吗| 91精品国产91久久久久| 里番精品3d一二三区| 韩国三级日本三级少妇99| 日韩免费精品| 日本久久久a级免费| 麻豆一区二区| 欧美精品videosex极品1| 人成福利视频在线观看| 在线视频欧美区| 欧美中日韩在线| 97偷自拍亚洲综合二区| 999精品视频一区二区三区| 在线观看的日韩av| 涩涩日韩在线| 欧美在线黄色| 2021狠狠干| 91小视频在线观看| 99精产国品一二三产品香蕉| 天天亚洲美女在线视频| 91免费网站视频| 久久综合久久久久88| 艹b视频在线观看| 天天色综合成人网| 91免费在线| 亚洲人成在线观看一区二区| 美女的诞生在线观看高清免费完整版中文| 久久精品国产成人一区二区三区| 在线看的黄色网址| 日韩免费成人网| 99国产**精品****| 成人18免费| 久久99国产综合精品女同| 亚洲精品三级| 中文字幕中文字幕在线中文字幕三区| 亚洲人成在线观| 青青国产91久久久久久| 十九岁完整版在线观看好看云免费| 7777kkkk成人观看| 久久中文娱乐网| 99er精品视频| 国产日产欧美视频| 久久精品国产一区二区电影| 国产乱码精品一区二区三区忘忧草 | 久久手机在线视频| 亚洲第一福利视频| 极品av少妇一区二区| 欧美性videos| 影音先锋亚洲视频| 伊人久久精品视频| 99久久国产综合精品色伊| 久久xxx视频| 污版视频在线观看| 天天好比中文综合网| 日韩天堂在线观看| av电影天堂一区二区在线观看| 国产日本久久| 男捅女免费视频| 国产精品视频一区二区三区四| 亚洲六月丁香色婷婷综合久久| 欧美久久一区| 极品在线视频| 久在线观看视频| 2019最新中文字幕| 精品福利在线观看| 久久久久久影院| 在线天堂资源| 深夜福利视频在线免费观看| 在线视频不卡一区二区| 欧美有码在线观看| 亚洲第一页在线| 成人三级在线视频| 2020最新国产精品| 男生女生差差差的视频在线观看| 亚洲一区二区三区精品视频 | 天天插天天狠天天透| 国产精品jvid在线观看蜜臀| 国产清纯白嫩初高生在线观看91 | 日本不卡免费高清视频| 色老汉av一区二区三区| 国产二区国产一区在线观看| 精品在线手机视频| 高清精品在线| 色狠狠久久av五月综合|| 精品国产乱码久久久久久天美| 国产丝袜在线| 成人h视频在线| 亚洲精品欧美综合四区| 日本高清中文字幕在线| 91亚洲精品一区二区| 亚洲午夜精品网| 亚洲人和日本人hd| 2025韩国理伦片在线观看| 亚洲色图欧美制服丝袜另类第一页| 成人精品一区二区三区中文字幕 | 91影院在线观看| 粉嫩av一区二区三区四区五区| 四虎影院一区二区三区 | 一区二区三区四区在线视频| 91成人免费在线| 五月开心六月丁香综合色啪| 蜜桃视频在线观看网站| 亚州精品天堂中文字幕| 92精品国产成人观看免费| 黄视频在线观看免费| 亚洲人线精品午夜| 国产日韩欧美一区| 色呦呦在线免费观看| 一区二区在线观看网站| 久久亚洲春色中文字幕| 国产一区二区在线视频| 在线男人天堂| 黄色视屏网站在线免费观看| 国产亚洲欧美日韩精品一区二区三区 | 成人欧美一区二区三区视频网页| 无遮挡爽大片在线观看视频| 色大师av一区二区三区| 制服丝袜中文字幕一区| 99视频精品免费观看| 日韩在线观看www| 久久精品一二三区| 日韩午夜电影av| 黄一区二区三区| 二吊插入一穴一区二区| 人妻夜夜添夜夜无码av| www.久久久久| 国产片一区二区三区| 东京久久高清| 黄色成人av| 91久久久久久久| 欧美三级乱人伦电影| 亚洲高清在线| 久cao在线| 色香蕉在线观看| 亚洲性夜色噜噜噜7777| 丁香激情综合五月| 成人激情久久| jizzzz日本| 国产精品久久久久久久久久新婚| 亚洲va在线va天堂| 欧美精品网站| 成人高潮aa毛片免费| 日本一本中文字幕| 欧美激情综合色| 图片区日韩欧美亚洲| 久久九九免费| 波多野在线观看| a黄色在线观看| hd国产人妖ts另类视频| 在线观看免费版| 3d黄动漫网站| 免费看毛片的网址| 美国av一区二区三区| 日韩美女免费线视频| 欧美亚洲国产视频| 高清在线视频日韩欧美| 8050国产精品久久久久久| 国产午夜精品久久| 色一区二区三区四区| 怡红院av在线| 亚洲精品高清无码视频| 亚洲最大av网站| 日韩精品中文字幕有码专区| 国产精品护士白丝一区av| 黄色精品一区| 欧美黑粗硬大| 久草视频视频在线播放| 国产91视频一区| 国产精品一区二区女厕厕| 日韩精品在线观看网站| 一区二区三区四区五区视频在线观看| 国产精品亚洲产品| 99这里只有精品视频| 亚洲s色大片| 国产又大又黄又猛| 奇米视频888战线精品播放| 欧美精品日韩三级| 这里只有精品电影| 综合亚洲深深色噜噜狠狠网站| 亚洲欧美久久| 一道本一区二区三区| 范冰冰一级做a爰片久久毛片| 资源视频在线播放免费| 黄色一级片播放| 日本视频一区在线观看| 国产精品1234|