【问题标题】:How to get TypeScript to infer the types through multiple Higher Order components in Typescript?如何让 TypeScript 通过 Typescript 中的多个高阶组件来推断类型?
【发布时间】:2019-10-03 01:14:59
【问题描述】:

我正在将一个 React 应用程序转换为使用大量函数式编程模式的 Typescript。这些模式中最重要的是将多个高阶组件组合在一起以返回增强组件的能力。我想要发生的事情是让 Typescript 在每个级别推断组件的类型,并以我传递的组件完成,以拥有其上方的所有 HOC 类型。

我已在 3.5.0 和最新版本的 Typescript 上尝试过此代码。代码方面,我尝试让泛型扩展我想要的类型 <T extends FooProps>,将 FC<T & BarProps> 传递给返回功能组件,并反转 HOC 的调用。

export const withFoo = <T>(Component: FC<T & FooProps>): FC<Omit<T, 'foo'>> => props => (
  <Component {...props as T} foo="foo" />
)

export const withBar = <T>(Component: FC<T & BarProps>): FC<Omit<T, 'bar'>> => props => (
  <Component {...props as T} bar="bar" />
)

export const EnhancedComponent = withFoo(
  withBar(({ bar, foo }) => { // bar exists and foo does not exist on type 'PropsWithChildren<BarProps>'
    console.log(bar) // types come through (bar: string)
    console.log(foo) // types don't come through (foo: any) :(
    return <div>woohoo</div> 
  }),
)

我希望这些类型在 HOC 的每个级别都通过。在上面的示例中,我想在我传递的功能组件中看到这一点:

export const EnhancedComponent = withFoo(
  withBar(({ bar, foo }) => { // both exist on the component
    console.log(bar) // bar: string
    console.log(foo) // foo: string
    return <div>woohoo</div>
  }),
)

【问题讨论】:

  • 个人意见:像这样堆叠 HOC 使用新的 hooks api 来共享有状态逻辑是 100% 过度杀伤
  • 我同意钩子在大多数情况下都很好,但在这种情况下,我们希望根据父级的结果有条件地渲染其他 HOC。我们还希望能够渲染组件来处理错误和加载状态,而不是在渲染每个组件时处理它们,这就是我们使用 HOC 的原因。

标签: reactjs typescript function-composition higher-order-components


【解决方案1】:

寻找高阶组件类型的一个好起点是其他包或@types 包。

但是,您的用例有点有趣,因为您希望被包装的组件接收更多的道具,并且每个包装的高阶组件都提供这些值之一。

鉴于此,这是一种潜在的解决方案。需要指出的几点:

  • 假设您想要允许任何组件类型(类或函数),那么您可能想要找到React.ComponentType
  • 我尽量避免强制转换,但看起来这仍然是 TypeScript 的一个缺点(问题)。这两个问题看起来很相关:#28884 & #28748
  • 添加Omit 以供参考,因为在3.5 之前它不存在(我在Codesandbox 中进行测试,此时仍在3.3
import * as React from "react";
import { render } from "react-dom";

// This exists in TypeScript 3.5+
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

interface FooProps {
  foo: string;
}

interface BarProps {
  bar: string;
}

export const withFoo = <Props extends FooProps>(
  Component: React.ComponentType<Props>
): React.ComponentType<Omit<Props, keyof FooProps>> => props => (
  <Component {...props as Props} foo="foo" />
);

export const withBar = <Props extends BarProps>(
  Component: React.ComponentType<Props>
): React.ComponentType<Omit<Props, keyof BarProps>> => props => (
  <Component {...props as Props} bar="bar" />
);

const WrappedComponent = ({ bar, foo, more }) => {
  console.log(more);
  console.log(bar);
  console.log(foo);
  return <div>woohoo</div>;
};

const EnhancedComponent = withFoo(withBar(WrappedComponent));

const App = () => <EnhancedComponent more="more" />;

const rootElement = document.getElementById("root");
render(<App />, rootElement);

【讨论】:

  • 感谢您试一试。我尝试了您的解决方案,但看起来它正在丢失类型。这是一个 Codesandbox 链接:codesandbox.io/s/pensive-bas-h8psx?fontsize=14
  • 嗯,是的,我不确定确切的原因,但有一些相当复杂的类型。是否有任何理由必须在对 HOC 的调用中定义组件?在我的解决方案中,我在外面声明了它,这似乎按预期工作:const Wrapped = ({ bar, foo, more }) =&gt; { return null; }; const EnhancedComponent = withFoo(withBar(Wrapped));
  • 原因是因为我们希望在包装组件中推断类型。通过在 HOC 之外声明 Wrapped 组件,您将失去传递给它的 props 的类型安全性。
猜你喜欢
  • 2019-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-03
  • 2021-11-23
  • 1970-01-01
  • 2018-10-20
  • 2012-10-25
相关资源
最近更新 更多