无法正确键入以下 HOC 函数

Can't properly type the following HOC function

我写了一个我无法正确输入的 HOC 函数(这是预料之中的,因为我是 TypeScript 初学者,甚至是类型初学者)。这是函数:

const withLayout = (LayoutComponent, layoutProps = {}) => (WrappedComponent) => {
  return function WithLayout(props) {
    return (
      <LayoutComponent {...layoutProps}>
        <WrappedComponent {...props} />
      </LayoutComponent>
    )
  }
}

我是这样使用的:

interface LayoutProps { a: string, b: string }

interface IndexProps { c: string, d: string }

class Layout extends React.Component<LayoutProps> { }

class Index extends React.Component<IndexProps> { }

withLayout(Layout, { a: 'a', b: 'b' })(Index)

这是我尝试输入的:

const withLayout = <LP extends JSX.IntrinsicAttributes & { children?: React.ReactNode }>(
  LayoutComponent: React.ComponentType<LP>,
  layoutProps: LP = {}
) => <WP extends JSX.IntrinsicAttributes & { children?: React.ReactNode }>(
  WrappedComponent: React.ComponentType<WP>
) => {
    return function WithLayout(props: WP): JSX.Element {
      return (
        <LayoutComponent {...layoutProps}>
          <WrappedComponent {...props} />
        </LayoutComponent>
      )
    }
  }

我有几个问题,正如您在 TypeScript playground 中看到的那样。我意识到我应该允许 layoutPropsprops 分别成为 LayoutComponentWrappedComponent 道具的对象,但我真的想不出正确的方法将它传达给 TypeScript。有什么建议吗?

问题 #1:默认道具

参数 layoutProps 必须是 LP 类型,其中 LP 是布局组件的属性。您不能使用空对象作为 layoutProps 的默认值,因为它没有所需的道具。我们根本不能有默认值,因为我们不知道 LP 是什么,所以我们不可能满足未知类型的要求。

问题 #2:extends 类型

您声明 LayoutComponentWrappedComponent 的属性必须扩展 JSX.IntrinsicAttributes & { children?: React.ReactNode }。此类型中没有必需的属性,因此技术上您的组件应该可以分配给它,但缺少重叠会导致 Typescript 类型推断阻塞。

我们应该得到更有用的错误,例如“这些类型没有共同的属性”。相反,它只是不推断类型。它使用你的 extends 子句作为类型,然后给你一个错误,这些道具 JSX.IntrinsicAttributes & { children?: React.ReactNode } 不能用作组件的道具,因为它们缺少组件所需的属性。

我们可以简单地通过扩大 extends 条件(甚至完全删除它)来解决这个问题。使用 LP extends {},可以正确推断组件的实际 props。

const withLayout = <LP extends {}>(
  LayoutComponent: React.ComponentType<LP>,
  layoutProps: LP
) => <WP extends {}>(
  WrappedComponent: React.ComponentType<WP>
) => {
    return function WithLayout(props: WP): JSX.Element {
      return (
        <LayoutComponent {...layoutProps}>
          <WrappedComponent {...props} />
        </LayoutComponent>
      )
    }
  }

Typescript Playground Link