如何用 HOC 包装每个导出的组件?
Posted
技术标签:
【中文标题】如何用 HOC 包装每个导出的组件?【英文标题】:How to wrap every exported comopnent with HOC? 【发布时间】:2020-06-06 01:22:38 【问题描述】:我需要为我的 React 函数组件的 ALL 添加添加 [data-test-id]
属性以进行测试的可能性。为了实现这一点,我创建了 withTestId()
HOC,它将可选的 prop testId
添加到包装的组件中,并在定义后将 [data-test-id]
添加到最终的 html 中。
所以当我定义像这样的组件时:
<ExampleComponent testId="example" />
它返回:
<div data-test-id="example" />
我唯一的问题是将其应用于每个组件,而无需将其单独包装在每个组件中。所以不要写这样的代码:
function ExampleComponent() ...
export default withTestId(ExampleComponent)
我想将我的所有导出文件都包含在我的 index.ts
文件中,现在看起来像这样:
export default as ExampleComponent from "./ExampleComponent";
export default as ExampleComponent2 from "./ExampleComponent2";
...
我怎样才能做到这一点?
【问题讨论】:
如果你想在任何地方都使用 HoC,我不确定它是否适合。 那你还有什么推荐的? 默认情况下数据属性已经是有效的 props,为什么不只是<TestComponent data-test-id="123" />
并确保使用 <div ...props>
将未知的 props 应用于每个组件的根元素?不再需要 HoC,它适用于您想要应用于组件的任何内容。虽然这是一个risky pattern,但取决于您管理道具的程度。我个人经常使用它,但我也在组件树的所有不同级别过滤掉任何不需要的道具。
如果你有 props 类型验证,当无意的额外 props 被传递时你会收到警告或错误,所以jsx-props-no-spreading
可以安全地禁用。它仍然可能会影响可维护性,但会大大减少样板文件、树深度(每个 HoC 都会增加)、改进 tree-shaking(导出所有内容的索引会大大削弱 tree-shaking),并且总体上会降低复杂性因为与导出代码生成等相比,传播是一种常见模式。
我想我会使用这种方法。你是对的,当我进行类型验证时使用它是安全的。当然,这条规则有它的优点和缺点,但我开始认为禁用它比让它打开有更多积极的方面。
【参考方案1】:
我看到了两种方法;一种动态方式,使您的库的用户代码更加复杂。您可以轻松更改实现,并使用更多样板代码更改另一个实现,保持用户代码不变。
我还没有测试他们在捆绑代码时关于摇树的行为。
在用户代码中使用破坏
这允许在您的主要组件导出文件中添加/删除内容,而不必担心库中的其他样板。高阶组件可以轻松打开/关闭。一个警告:用户代码需要使用解构来检索组件。
您的新 index.ts
文件看起来像这样,而我在同一目录中调用了您以前的 index.ts
文件 components.ts
:
import * as RegularComponents from "./components";
import withTestId from "./with-test-id";
const WithTestIdComponents = Object
.keys(RegularComponents)
.reduce((testIdComps, key) =>
return
...testIdComps,
[key]: withTestId(RegularComponents[key])
;
, );
export default WithTestIdComponents;
在您的应用程序代码中使用它:
import MyComponents from "./components/tested";
const Component1, Component2, Component3, Component4 = MyComponents;
这使用了default
导出,使您看起来像在一个地方拥有所有组件,但由于您无法直接解构导出,因此您需要第二步来从中获取正确的组件。
将样板文件添加到导出文件
由于有一个index.ts
文件包含库中导出的所有组件,因此可以导入/重命名每个组件并使用withTestId
及其名称重新导出它们:
import withTestId from "./with-test-id";
import default as TmpComponent1 from "./component1";
import default as TmpComponent2 from "./component2";
import default as TmpComponent3 from "./component3";
import default as TmpComponent4 from "./component4";
export const Component1 = withTestId(TmpComponent1);
export const Component2 = withTestId(TmpComponent2);
export const Component3 = withTestId(TmpComponent3);
export const Component4 = withTestId(TmpComponent4);
这样,导入就可以像以前一样使用了:
import
Component1,
Component2,
Component3,
Component4
from "./components";
我认为使用index
文件已经是某种样板文件,这种方法增加了它。由于用户代码不需要任何更改,因此我倾向于这种方法。
在我们的一个项目中,我们使用自定义 takeoff 脚本来为我们创建这种样板文件,每当我们生成新组件时。
示例
这里是code sandbox,可以查看这两种方法。
【讨论】:
感谢您的回复。我也认为第二种方法会是更好的选择。我将尝试编写一些脚本来为我生成这些index.ts
文件——这听起来是最好的解决方案。当您使用 takeoff
脚本时,您必须手动运行它吗?或者当新组件到达时,您是否有某种观察者正在运行脚本?
我使用 takeoff 同时生成组件和导出。 CLI 可以调用您的脚本并生成多个文件或更改某些 index.ts 文件等。以上是关于如何用 HOC 包装每个导出的组件?的主要内容,如果未能解决你的问题,请参考以下文章
如何用样式化组件和 TypeScript 包装 Ant Design?