如何划分组件的通用属性并强类型化结果

How to partition a component's generic props and strongly type the results

我正在尝试将自定义类型划分回它的各个元素:

type CustomType<T extends React.ElementType> = React.ComponentPropsWithoutRef<T> & { aBunchOfProps: string; }

代码如下所示:

const partitionProps = <T extends React.ElementType>(
  props: CustomType<T>
): {
  customProps: { aBunchOfProps: string }, // named type
  componentProps: ComponentPropsWithoutRef<T>
} => {
  const {
    aBunchOfProps,
    ...componentProps
  } = props;

  const customProps = { aBunchOfProps };

  return { customProps, componentProps };
} // Error! componentProps: Omit<CustomType<T>, { aBunchOfProps }> is 
  // not assignable to type ComponentPropsWithoutRef<T>

这很奇怪,因为我能够断言类型相等

type Equals<T, U> = T extends U ? (U extends T : true : false) : false;

type AreTheyEqual<T extends React.ElementType> = Equals<
  Omit<CustomType<T>, { aBunchOfProps: string }>,
  React.ComponentPropsWithoutRef<T>
>;
type UsingDiv = AreTheyEqual<'div'>; // true
type UsingA = AreTheyEqual<'a'>; // true
type UsingIFrame = AreTheyEqual<'iframe'>; //true

也许应该有一些条件类型来断言分区函数中的类型相等,但我不太明白

type AssertEquality<T extends React.ElementType> = Equals<T1, T2> extends true ? React.ComponentPropsWithoutRef<T> : never;

但这也不太奏效。

有什么想法吗?


编辑 11/1/21:参见 this typescript playground for a reproduction。我们能够强制将 return 类型强制转换为 React.ComponentPropsWithoutRef<T>,但它仍然保留函数并存在于调用组件内部,如 any.

这里的问题是你在函数内部使用泛型

const partitionProps = <T extends React.ElementType>(
  props: CustomType<T>
): { /** ....some code */ }

T 就像黑盒子,只有在调用函数时才会知道,而比较 :

type UsingDiv = AreTheyEqual<'div'>; // true
type UsingA = AreTheyEqual<'a'>; // true
type UsingIFrame = AreTheyEqual<'iframe'>; //true

更容易,因为 AreTheyEqual 的泛型参数在编译时是已知的:divaiframe.

想象一下,我们的函数中没有泛型类型:

const partitionProps = (
    props: SomeHelpers & ComponentPropsWithoutRef<'a'>
  ): {
    customProps: { aBunchOfProps: string },
    componentProps: ComponentPropsWithoutRef<'a'>
  } => {
  const {
    aBunchOfProps,
    ...componentProps
  } = props;
  const customProps = { aBunchOfProps };

  return { customProps, componentProps };
} 

没有错误,因为 TS 能够推断出 componentProps.

的确切类型

一旦您提供了 T,TS 就不再确定类型是否相等。 因为, return

是不安全的
{
    customProps: { aBunchOfProps: string },
    componentProps: ComponentPropsWithoutRef<T>
  }

但是,您可以放宽严格的行为。您可以重载您的功能。重载是双变的。

type SomeHelpers = { aBunchOfProps: string };

type CustomType<T extends React.ElementType> = SomeHelpers &
  React.ComponentPropsWithoutRef<T>;

function partitionProps<T extends React.ElementType<{ tag: number }>>(
  props: CustomType<T>
): {
  customProps: { aBunchOfProps: string }; // named type
  componentProps: ComponentPropsWithoutRef<T>;
};
function partitionProps<T extends React.ElementType>(props: CustomType<T>) {
  const { aBunchOfProps, ...componentProps } = props;

  const customProps = { aBunchOfProps };

  return { customProps, componentProps };
}

const result = partitionProps({
  aBunchOfProps: 'sdf',
  tag: 42
});

请记住,您还应该为 React.ElementType 提供通用参数,否则通用参数将为 any。 见ElementType签名:

 type ElementType<P = any> =
        {
            [K in keyof JSX.IntrinsicElements]: P extends JSX.IntrinsicElements[K] ? K : never
        }[keyof JSX.IntrinsicElements] |
        ComponentType<P>;