[英]Correctly typing a HOC component that returns a component with forwardRef
我正在尝试制作一个 HOC,它返回一个带有 forwardRef 的组件,但我不确定如何输入它这是代码
type Omitted = 'variant' | 'size';
export interface InputProps<T extends Omit<T, Omitted>> {
startIcon?: React.ReactElement;
endIcon?: React.ReactElement;
}
const withInput = <P extends object>(InputComponent: React.ComponentType<P>) =>
React.forwardRef<HTMLInputElement, InputProps<P>>(
({ startIcon, endIcon, ...props }, ref) => {
return (
<InputGroup>
{startIcon && (
<InputLeftElement>
{startIcon}
</InputLeftElement>
)}
<InputComponent ref={ref} {...props} />
{endIcon && (
<InputRightElement>
{endIcon}
</InputRightElement>
)}
</InputGroup>
);
}
);
const Input = withInput(InputBaseComponent);
Input.Number = withInput(NumberInputBaseComponent);
但是在InputComponent
上遇到两个错误一个
Type '{ children?: ReactNode; ref: ((instance: HTMLInputElement | null) => void) | MutableRefObject<HTMLInputElement | null> | null; }' is not assignable to type 'P'.
'{ children?: ReactNode; ref: ((instance: HTMLInputElement | null) => void) | MutableRefObject<HTMLInputElement | null> | null; }' is assignable to the constraint of type 'P', but 'P' could be instantiated with a different subtype of constraint 'object'
另一个是在Input.Number
Property 'Number' does not exist on type 'ForwardRefExoticComponent<InputProps<Pick<InputProps, "variant" | "size" | "left" | "right" | "form" | "p" | "slot" | "style" | "title" | "pattern" | "ref" | "key" | "sx" | "accept" | "alt" | "autoComplete" | ... 514 more ... | "isLoading"> & Pick<...>> & RefAttributes<...>>'.
如果有人想尝试,这里有一个代码沙盒的链接: https ://codesandbox.io/s/dazzling-shape-rwmmg?file=/src/Input.tsx:0-959
Input.Number
错误更容易理解。 您正在尝试导出一个对象,该对象是一个组件并且具有一个不同的组件属性Number
。 错误告诉您不能在组件上设置像Number
这样的任意属性,这有点道理(这是可行的,但很复杂)。 我建议将Base
和Number
放在同一级别,而不是将一个作为另一个的属性。
const Input = {
Base: withInput(InputBaseComponent),
Number: withInput(NumberInputBaseComponent)
}
现在Input
只是一个具有两个不同组件作为属性的对象。
您还可以单独导出各种实例,并在导入时将它们组合在一起
import * as Input from `./Input`
首先,我认为您的InputProps
接口没有按照您的意图执行。
export interface InputProps<T extends Omit<T, Omitted>> {
startIcon?: React.ReactElement;
endIcon?: React.ReactElement;
}
这个接口表示InputProps
是一个带有可选startIcon
和endIcon
的对象。 就是这样。 T
从来没有被使用过,也没有关系。 InputProps
不包含T
道具。 当您将InputProps
传播到{ startIcon, endIcon, ...props }
中唯一剩下的...props
是children
。 因此,当我们希望...props
包含InputComponent
所有 props P
,这当然会导致错误。
我想你在这里想说的是InputProps<T>
包含这两个图标和所有T
,除了省略的属性。
export type InputProps<T> = Omit<T, Omitted> & {
startIcon?: React.ReactElement;
endIcon?: React.ReactElement;
}
这更好,因为现在我们在...props
有一些实际的...props
。 悬停在它显示
Pick<React.PropsWithChildren<InputProps<P>>, "children" | Exclude<Exclude<keyof P, Omitted>, "startIcon" | "endIcon">>
所以基本上我们已经得到了P
所有内容加上children
减去variant
、 size
、 startIcon
和endIcon
。 这个子集可以分配给P
吗? 也许,但并非总是如此,这就是错误告诉你的。
当使用{...rest}
语法时,打字稿很难理解这些部分构成了整体,这是很常见的。 我经常不得不在我的 HOC 中声明as P
无论接下来几段中的所有内容如何,您最终仍将在此处执行此操作:
<InputComponent {...props as P} ref={ref} />
真的可以说...props
是P
吗? 当我们使用as
,我们基本上是在告诉 typescript 安静,因为我们知道的比它多。 因此,我们希望确保不会向其提供不良信息。
什么时候 {...props}不能分配给P
? 我们如何防止这些情况发生?
由于我们从内部组件的 props 中Omit
variant
和size
,所以这里有一些值得关注的原因,所以...props
(我们希望它是P
)被打字稿知道不包含这些属性中的任何一个。 如果这些属性存在于type P
并且是必需的,那么...props
不能是P
。 如果P
包含必需的属性startIcon
和endIcon
,因为我们将它们与价差一起取出。
对于variant
和size
,我根本看不出省略它们的意义。 您没有为它们设置任何默认值,那么为什么不让它们通过呢?
但是作为一般的想法,对于startIcon
和endIcon
,我们需要改进我们的P extends
这样如果P
具有这些属性,它们必须是可选的。 我们还想确保我们的InputComponent
可以接受我们传递给它的ref
类型。
你可以更有信心,我们与声称as
是准确的,如果你细化类型是这样的:
interface Dropped {
variant?: any;
size?: any;
}
interface Icons {
startIcon?: React.ReactElement;
endIcon?: React.ReactElement;
}
export type InputProps<T> = Omit<T, keyof Dropped | "ref"> & Icons;
type PBase = {
ref?: React.Ref<HTMLInputElement>;
} & Dropped & Partial<Record<keyof Icons, any>>
const withInput = <P extends PBase>(InputComponent: React.ComponentType<P>) => ....
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.