怎么编写插件机制优化基于Antd Table封装表格的混乱代码

lxf2023-03-18 14:54:31
  • 鸿蒙官方战略合作共建——HarmonyOS技术社区

  • 逻辑解耦,把每个小功能的代码整合到插件文件中去,不和组件耦合起来,增加可维护性。

  • 用户共建,内部使用的话方便同事共建,开源后方便社区共建,当然这要求你编写的插件机制足够完善,文档足够友好。

  • 不过插件也会带来一些缺点,设计一套完善的插件机制也是非常复杂的,像 WEBpack、Rollup、Redux  的插件机制都有设计的非常精良的地方可以参考学习。

    接下来,我会试着实现的一个最简化版的插件系统。

    源码

    首先,设计一下插件的接口:

    export interface TreeTablePlugin<T = any> {   (props: ResolvedProps, context: TreeTablePluginContext): {          onColumn?(column: ColumnProps<T>): void;          onRecord?(record): void;          onExpand?(expanded, record): void;          components?: TableProps<T>['components'];   }; }  export interface TreeTablePluginContext {   forceUpdate: React.DispatchWithoutAction;   replaceChildList(record, childList): void;   expandedRowKeys: TableProps<any>['expandedRowKeys'];   setExpandedRowKeys: (v: string[] | number[] | undefined) => void; }

    我把插件设计成一个函数,这样每次执行都可以拿到最新的 props 和 context。

    context 其实就是组件内一些依赖上下文的工具函数等等,比如 forceUpdate, replaceChildList  等函数都可以挂在上面。

    接下来,由于插件可能有多个,而且内部可能会有一些解析流程,所以我设计一个运行插件的 hook 函数 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);       }     },          mergeComponents() {       let components: TableProps<any>['components'] = {};       for (const plugin of plugins) {         components = deepmerge.all([           components,           plugin.components || {},           props.components || {},         ]);       }       return components;     },   };    return container; };

    目前的流程很简单,只是把每个 plugin 函数调用一下,然后提供对外的包装接口。mergeComponent 使用deepmerge[4]  这个库来合并用户传入的 components 和 插件中的 components,暂时不做冲突处理。

    接着就可以在组件中调用这个函数,生成 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   );  })