如何用 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,为什么不只是 &lt;TestComponent data-test-id="123" /&gt; 并确保使用 &lt;div ...props&gt; 将未知的 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 包装每个导出的组件?的主要内容,如果未能解决你的问题,请参考以下文章

使用 HOC 与使用组件包装之间的区别

通过 HOC 将 React 上下文传递给包装的组件

如何用样式化组件和 TypeScript 包装 Ant Design?

反应如何在 HOC 中获取包装组件的高度?

使用 HOC 包装时,组件不会从 getStaticProps 接收道具

使用 connect 和 withRouter 包装组件的顺序是不是重要