鸿蒙官方战略合作共建——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 ); })