溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么編寫插件機制優(yōu)化基于Antd Table封裝表格的混亂代碼

發(fā)布時間:2021-10-18 11:21:50 來源:億速云 閱讀:161 作者:iii 欄目:web開發(fā)

本篇內容介紹了“怎么編寫插件機制優(yōu)化基于Antd Table封裝表格的混亂代碼”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

前言

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

  • 每個層級縮進指示線

  • 遠程懶加載子節(jié)點

  • 每個層級支持分頁

最后實現(xiàn)的效果大概是這樣的:

怎么編寫插件機制優(yōu)化基于Antd Table封裝表格的混亂代碼

最終效果

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

重構思路

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

  • 指引線的邏輯分散在 rewriteColumns, components中。

  • 分頁的邏輯被分散在 rewriteColumns 和 rewriteTree 中。

  • 加載更多的邏輯被分散在 rewriteTree 和 onExpand 中

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

export const TreeTable = rawProps => {   function rewriteTree() {     // ?加載更多邏輯     // ? 分頁邏輯   }    function rewriteColumns() {     // ? 分頁邏輯     // ? 縮進線邏輯   }    const components = {     // ? 縮進線邏輯   };    const onExpand = async (expanded, record) => {     // ? 加載更多邏輯   };    return <Table />; };

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

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

// ? 分頁邏輯 const usePaginationPlugin = () => {}; // ? 加載更多邏輯 const useLazyloadPlugin = () => {}; // ? 縮進線邏輯 const useIndentLinePlugin = () => {};  export const TreeTable = rawProps => {   usePaginationPlugin();    useLazyloadPlugin();    useIndentLinePlugin();    return <Table />; };

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

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

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

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

我們設計插件機制,想要實現(xiàn)這兩個目標:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術社區(qū)

  2. 邏輯解耦,把每個小功能的代碼整合到插件文件中去,不和組件耦合起來,增加可維護性。

  3. 用戶共建,內部使用的話方便同事共建,開源后方便社區(qū)共建,當然這要求你編寫的插件機制足夠完善,文檔足夠友好。

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

接下來,我會試著實現(xiàn)的一個最簡化版的插件系統(tǒng)。

源碼

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

export interface TreeTablePlugin<T = any> {   (props: ResolvedProps, context: TreeTablePluginContext): {     /**      * 可以訪問到每一個 column 并修改      */     onColumn?(column: ColumnProps<T>): void;     /**      * 可以訪問到每一個節(jié)點數(shù)據      * 在初始化或者新增子節(jié)點以后都會執(zhí)行      */     onRecord?(record): void;     /**      * 節(jié)點展開的回調函數(shù)      */     onExpand?(expanded, record): void;     /**      * 自定義 Table 組件      */     components?: TableProps<T>['components'];   }; }  export interface TreeTablePluginContext {   forceUpdate: React.DispatchWithoutAction;   replaceChildList(record, childList): void;   expandedRowKeys: TableProps<any>['expandedRowKeys'];   setExpandedRowKeys: (v: string[] | number[] | undefined) => void; }

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

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

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

export const usePluginContainer = (   props: ResolvedProps,   context: TreeTablePluginContext ) => {   const { plugins: rawPlugins } = props;    const plugins = rawPlugins.map(usePlugin => usePlugin?.(props, context));    const container = {     onColumn(column: ColumnProps<any>) {       for (const plugin of plugins) {         plugin?.onColumn?.(column);       }     },     onRecord(record, parentRecord, level) {       for (const plugin of plugins) {         plugin?.onRecord?.(record, parentRecord, level);       }     },     onExpand(expanded, record) {       for (const plugin of plugins) {         plugin?.onExpand?.(expanded, record);       }     },     /**      * 暫時只做 components 的 deepmerge      * 不處理自定義組件的沖突 后定義的 Cell 會覆蓋前者      */     mergeComponents() {       let components: TableProps<any>['components'] = {};       for (const plugin of plugins) {         components = deepmerge.all([           components,           plugin.components || {},           props.components || {},         ]);       }       return components;     },   };    return container; };

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

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

export const TreeTable =  React.forwardRef((props, ref) => {   const [_, forceUpdate] = useReducer((x) => x + 1, 0)    const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([])    const pluginContext = {     forceUpdate,     replaceChildList,     expandedRowKeys,     setExpandedRowKeys   }    // 對外暴露工具方法給用戶使用   useImperativeHandle(ref, () => ({     replaceChildList,     setNodeLoading,   }));    // 這里拿到了 pluginContainer   const pluginContainer = usePluginContainer(     {       ...props,       plugins: [usePaginationPlugin, useLazyloadPlugin, useIndentLinePlugin],     },     pluginContext   );  })

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

export const TreeTable = React.forwardRef((props, ref) => {   // 省略上一部分代碼&hellip;&hellip;    // 這里拿到了 pluginContainer   const pluginContainer = usePluginContainer(     {       ...props,       plugins: [usePaginationPlugin, useLazyloadPlugin, useIndentLinePlugin],     },     pluginContext   );    // 遞歸遍歷整個數(shù)據 調用鉤子   const rewriteTree = ({     dataSource,     // 在動態(tài)追加子樹節(jié)點的時候 需要手動傳入 parent 引用     parentNode = null,   }) => {     pluginContainer.onRecord(parentNode);      traverseTree(dataSource, childrenColumnName, (node, parent, level) => {       // 這里執(zhí)行插件的 onRecord 鉤子       pluginContainer.onRecord(node, parent, level);     });   }    const rewrittenColumns = columns.map(rawColumn => {     //  這里把淺拷貝過后的 column 暴露出去     //  防止污染原始值     const column = Object.assign({}, rawColumn);     pluginContainer.onColumn(column);     return column;   });    const onExpand = async (expanded, record) => {     // 這里執(zhí)行插件的 onExpand 鉤子     pluginContainer.onExpand(expanded, record);   };    // 這里獲取合并后的 components 傳遞給 Table   const components = pluginContainer.mergeComponents() });

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

export const usePaginationPlugin: TreeTablePlugin = (   props: ResolvedProps,   context: TreeTablePluginContext ) => {   const { forceUpdate, replaceChildList } = context;   const {     childrenPagination,     childrenColumnName,     rowKey,     indentLineDataIndex,   } = props;    const handlePagination = node => {     // 先加入渲染分頁器占位節(jié)點   };    const rewritePaginationRender = column => {     // 改寫 column 的 render     // 渲染分頁器   };    return {     onRecord: handlePagination,     onColumn: rewritePaginationRender,   }; };

也許機智的你已經發(fā)現(xiàn),這里的插件是以 use 開頭的,這是自定義 hook的標志。

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

這是因為我們是在 usePluginContainer 中通過函數(shù)調用執(zhí)行各個 usePlugin,完全符合 React Hook 的調用規(guī)則。

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

export const useLazyloadPlugin: TreeTablePlugin = (   props: ResolvedProps,   context: TreeTablePluginContext ) => {   const { childrenColumnName, rowKey, hasNextKey, onLoadMore } = props;   const { replaceChildList, expandedRowKeys, setExpandedRowKeys } = context;    // 處理懶加載占位節(jié)點邏輯   const handleNextLevelLoader = node => {};    const onExpand = async (expanded, record) => {     if (expanded && record[hasNextKey] && onLoadMore) {       // 處理懶加載邏輯     }   };    return {     onRecord: handleNextLevelLoader,     onExpand: onExpand,   }; };

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

export const useIndentLinePlugin: TreeTablePlugin = (   props: ResolvedProps,   context: TreeTablePluginContext ) => {   const { expandedRowKeys } = context;   const onColumn = column => {     column.onCell = record => {       return {         record,         ...column,       };     };   };    const components = {     body: {       cell: cellProps => (         <IndentCell           {...props}           {...cellProps}           expandedRowKeys={expandedRowKeys}         />       ),     },   };    return {     components,     onColumn,   }; };

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

此時的目錄結構:

怎么編寫插件機制優(yōu)化基于Antd Table封裝表格的混亂代碼

目錄結構

“怎么編寫插件機制優(yōu)化基于Antd Table封裝表格的混亂代碼”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI