看到 Ant Design 核心成员 @zombieJ 昨天更新了一篇文章,提到一个遗留的 bug,用了 ConfigProvider 之后,即使没有用到 Form 组件,也会错误打包 rc-field-form。
我看完这篇文章,特意分析了之前云课堂的业务工程,发现也有这个问题。如果你也在用 Vita-cli 构建前端工程,可以按下面的步骤排查:
1、首先在 vita.config.js 中添加如下配置,禁用 scope hoisting,方便分析打包产物:
config.optimization.concatenateModules(false);
2、运行下面的命令,可以在打包完成后分析打包产物:
$ npx vita build --analyze
打包完成后会自动打开下面的页面,可以看到 rc-field-form 确实是被错误打包了(这个工程没有用到 Form 组件)
为啥会有这个问题?这是因为 ConfigProvider 提供了全局化配置能力,其中也包含了 Form 组件校验信息的自定义模板配置:
<ConfigProvider form={{ validateMessages }} />
从源码看出,ConfigProvider 底层依赖了从 rc-field-form 引入的 FormProvider,而 FormProvider 本身又对 rc-field-form 的 FormContext 进行了封装,导致引入 FormProvider 后将 rc-field-form 的更多内容给打包进来了:
有同学可能会想到,我们是不是可以做一个优化,如果没有配置 validateMessages 我们就不调用这个 FormProvider?
import { FormProvider } from 'rc-field-form';
const ConfigProvider = ({ validateMessages, children }) => {
let node = children;
if (validateMessages) {
node = <FormProvider validateMessages={merge(...)}>{node}</FormProvider>;
}
return node;
};
很遗憾,这是不行的。Tree Shaking 只在编译阶段静态分析(Static Code Analysis),不能执行代码,而 validateMessages 是一个运行时的配置。所以在打包过程中,我们无法知道 validateMessages 是否存在,因而无法做到这样的优化。
如何优化?作者给出了一种解决方案,调整依赖,让 FormProvider 解耦,ConfigProvider 不再依赖于 FormProvider。实现也非常简单,既然这是 rc-field-form 所独有的,那么我们直接抽一个 Context 出来,让 ConfigProvider 不再感知 FormProvider 即可:
import { ValidateMessageContext } from '../form/context.ts';
const ConfigProvider = ({ validateMessages, children }) => {
const mergedValidateMessages = ...
return (
// Just use the proxy context
<ValidateMessageContext value={mergedValidateMessages}>
<SomeOtherProvider>{children}</SomeOtherProvider>
</ValidateMessageContext>
);
};
而在 Form 中,同样消费代理的 Context:
import Form, { FormProvider } from 'rc-field-form';
import { ValidateMessageContext } from './context';
export default (props) => {
const validateMessages = React.useContext(ValidateMessageContext);
return (
<FormProvider validateMessages={validateMessages}>
<Form {...props} />
</FormProvider>
);
};
依赖就变成了这样,从而实现了解耦:
最后谈谈个人看法。这个 ConfigProvider 显然是 antd 设计之初留下的 bug。为啥之前没人发现,这是因为在 antd 里面,Table、Form 这些都是高频组件,开发中后台项目必然会用,这些也是 antd 逻辑最复杂、代码量最大的组件(应该不太有人只为了用一个 Button 就引入整个 antd)。因此 antd 作者考虑易用性,会把 Form 组件配置提升到 ConfigProvider,也是合理的做法。这样做会导致一些问题,ConfigProvider 是运行时配置,会导致 Tree-Shaking 失效。文章最后作者给出了一个比较简单直接的解决方案,虽然支持 Tree-Shaking,但是使用又变得有些复杂了。所以还是那句话,软件工程没有银弹,性能的提升必然是以牺牲易用性、可维护性为代价的。