Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to write plug-in Mechanism to optimize chaotic Code based on Antd Table Encapsulation Table

2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article introduces the knowledge of "how to write plug-in mechanism to optimize the chaotic code based on Antd Table encapsulation table". In the operation of actual cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!

Preface

Recently, in a business requirement, I implemented these functions by writing code in mechanisms such as callback functions provided by Antd Table:

Indent indicator line at each level

Remote lazy loading child node

Paging is supported at each level

The final result is something like this:

Final effect

In this article I want to talk about some of my thoughts on decoupling code and writing plug-ins for components in this requirement.

Reconstruction thought

With the increase of writing functions, the logic is coupled to the callback functions of Antd Table.

The logic of the leader is scattered in rewriteColumns, components.

The logic of paging is scattered among rewriteColumns and rewriteTree.

Loading more logic is scattered among rewriteTree and onExpand

At this point, the number of lines of code for the component has also reached 300 lines. If you take a look at the structure of the code, it is already quite confusing:

Export const TreeTable = rawProps = > {function rewriteTree () {/ /? Load more logic /? Paging logic} function rewriteColumns () {/ /? Paging logic /? Indent logic} const components = {/ /? Indent logic}; const onExpand = async (expanded, record) = > {/ /? Load more logic}; return;}

At this point, the shortcomings were exposed, and it became extremely painful when I wanted to change or delete one of the functions, often jumping between functions.

Is there a mechanism that allows code to be aggregated by function points rather than scattered among functions?

/ /? Paging logic const usePaginationPlugin = () = > {}; / /? Load more logical const useLazyloadPlugin = () = > {}; / /? Indent logic const useIndentLinePlugin = () = > {}; export const TreeTable = rawProps = > {usePaginationPlugin (); useLazyloadPlugin (); useIndentLinePlugin (); return;}

Yes, it's very similar to the improvement made by VueCompositionAPI and React Hook in terms of logic decoupling, but in the form of this callback function, it doesn't seem easy to do.

At this point, I recall that some open source frameworks in the community provide plug-in mechanisms that seem to inject user logic at each callback time without going deep into the source code.

Such as Vite plug-in [1], Webpack plug-in [2] and even the familiar Vue.use () [3], they essentially expose some internal timing and attributes, allowing users to write some code to intervene in every time the framework runs.

So, can we consider exposing the timing of "dealing with each node, column, every onExpand" so that users can also intervene in these processes, rewrite some properties and call some internal methods to implement the above functions?

We designed the plug-in mechanism to achieve these two goals:

Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community

Logic decoupling, the code of each small function is integrated into the plug-in file, which is not coupled with the component and increases maintainability.

Co-built by users, it is convenient for colleagues to build it internally, and it is convenient for the community to build it after open source. Of course, this requires that the plug-in mechanism you write is perfect enough and the documentation is friendly enough.

However, plug-ins also bring some shortcomings, the design of a set of perfect plug-in mechanism is also very complex, such as Webpack, Rollup, Redux plug-in mechanism has a very well-designed place to learn.

Next, I will try to implement a simplified version of the plug-in system.

Source code

First, design the interface for the plug-in:

Export interface TreeTablePlugin {(props: ResolvedProps, context: TreeTablePluginContext): {/ * can access each column and modify * / onColumn? (column: ColumnProps): void; / * can access the data of each node * will execute * / onRecord? (record): void after initialization or new child nodes / * * callback function expanded by node * / onExpand? (expanded, record): void; / * Custom Table component * / components?: TableProps ['components'];} export interface TreeTablePluginContext {forceUpdate: React.DispatchWithoutAction; replaceChildList (record, childList): void; expandedRowKeys: TableProps [' expandedRowKeys']; setExpandedRowKeys: (v: string [] | number [] | undefined) = > void;}

I designed the plug-in as a function so that I can get the latest props and context for each execution.

Context is actually some context-dependent tool functions in the component, such as forceUpdate, replaceChildList and other functions can be hung on it.

Next, because there may be multiple plug-ins and there may be some parsing processes within, I design a hook function usePluginContainer that runs the plug-in:

Export const usePluginContainer = (props: ResolvedProps, context: TreeTablePluginContext) = > {const {plugins: rawPlugins} = props; const plugins = rawPlugins.map (usePlugin = > usePlugin?. (props, context)); const container = {onColumn (column: ColumnProps) {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) }}, / * only deepmerge of components for the time being * Cell that does not deal with custom components defined after conflict will overwrite the former * / mergeComponents () {let components: TableProps ['components'] = {} For (const plugin of plugins) {components = deepmerge.all ([components, plugin.components | | {}, props.components | | {},]);} return components;},}; return container;}

The current process is simple, just call each plugin function and provide an external wrapper interface. MergeComponent uses the deepmerge [4] library to merge the components passed in by the user and the components in the plug-in without conflict handling for the time being.

You can then call this function in the component to generate pluginContainer:

Export const TreeTable = React.forwardRef ((props, ref) = > {const [_, forceUpdate] = useReducer ((x) = > x + 1,0) const [expandedRowKeys, setExpandedRowKeys] = useState ([]) const pluginContext = {forceUpdate, replaceChildList, expandedRowKeys, setExpandedRowKeys} / / external exposure tool method for users to use useImperativeHandle (ref, () = > ({replaceChildList, setNodeLoading,})) / / here you get pluginContainer const pluginContainer = usePluginContainer ({... props, plugins: [usePaginationPlugin, useLazyloadPlugin, useIndentLinePlugin],}, pluginContext);})

After that, you can execute the corresponding hook function through pluginContainer at the appropriate location of each process:

Export const TreeTable = React.forwardRef ((props, ref) = > {/ / omit the previous part of the code. / / get pluginContainer const pluginContainer = usePluginContainer ({... props, plugins: [usePaginationPlugin, useLazyloadPlugin, useIndentLinePlugin],}, pluginContext); / / Recursively traverse the entire data call hook const rewriteTree = ({dataSource, / / manually pass in the parent reference parentNode = null,} when dynamically adding subtree nodes) = > {pluginContainer.onRecord (parentNode) TraverseTree (dataSource, childrenColumnName, (node, parent, level) = > {/ / onRecord hook pluginContainer.onRecord (node, parent, level) of the plug-in executed here;} const rewrittenColumns = columns.map (rawColumn = > {/ / here the shallow copy of column is exposed / / the original value of pollution prevention const column = Object.assign ({}, rawColumn); pluginContainer.onColumn (column)) Return column;}); const onExpand = async (expanded, record) = > {/ / the onExpand hook pluginContainer.onExpand (expanded, record) of the plug-in executed here;}; / / get the merged components and pass it to Table const components = pluginContainer.mergeComponents ()})

After that, we can directly abstract the logic related to previous paging into usePaginationPlugin:

Export const usePaginationPlugin: TreeTablePlugin = (props: ResolvedProps, context: TreeTablePluginContext) = > {const {forceUpdate, replaceChildList} = context; const {childrenPagination, childrenColumnName, rowKey, indentLineDataIndex,} = props; const handlePagination = node = > {/ / add the render pager placeholder first}; const rewritePaginationRender = column = > {/ / overwrite column's render / / render pager} Return {onRecord: handlePagination, onColumn: rewritePaginationRender,};}

As you may have noticed, the plug-ins here start with use, which is a sign of custom hook.

Yes, it is both a plug-in and a custom Hook. So you can use all the capabilities of React Hook, and you can also introduce third-party Hook from various communities in the plug-in to enhance your capabilities.

This is because we execute each usePlugin through function calls in usePluginContainer, which is in full compliance with the calling rules of React Hook.

The logic associated with lazy loading nodes can also be abstracted into useLazyloadPlugin:

Export const useLazyloadPlugin: TreeTablePlugin = (props: ResolvedProps, context: TreeTablePluginContext) = > {const {childrenColumnName, rowKey, hasNextKey, onLoadMore} = props; const {replaceChildList, expandedRowKeys, setExpandedRowKeys} = context; / / handle lazy loading placeholder node logic const handleNextLevelLoader = node = > {}; const onExpand = async (expanded, record) = > {if (expanded & record [hasNextKey] & & onLoadMore) {/ / handle lazy loading logic}} Return {onRecord: handleNextLevelLoader, onExpand: onExpand,};}

The logic related to the indent line is extracted into 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 = > (),} Return {components, onColumn,};}

At this point, the main function has been reduced to about 150 lines, and all the functions related to the new functions have been moved to the plug-in directory, whether you want to add or delete, switch functions have become very easy.

The directory structure at this time:

Directory structure

This is the end of the content of "how to write plug-ins to optimize the chaotic code of Antd Table-based encapsulation tables". Thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report