我正在尝试制作一个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<...>>'.
如果有人想尝试一下,这里是指向codesandbox的链接:https://codesandbox.io/s/dazzling-shape-rwmmg?file=/src/Input.tsx:0-959
答案 0 :(得分:0)
更容易理解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
从未使用过,也没有关系。 T
中不包含InputProps
的道具。当您将InputProps
扩展到{ startIcon, endIcon, ...props }
时,...props
中剩下的唯一道具是children
。因此,当我们希望...props
包含P
的所有道具InputComponent
时,这当然会导致错误。
我认为您在这里要说的是InputProps<T>
包含这两个图标以及T
的全部,除了省略的属性。
export type InputProps<T> = Omit<T, Omitted> & {
startIcon?: React.ReactElement;
endIcon?: React.ReactElement;
}
这更好,因为现在...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
时,我们基本上是在告诉打字稿安静,因为我们知道的不止于此。因此,我们要确保不会给您错误的信息。
{... props} 不何时可以分配给P
?我们如何防止这些实例发生?
由于我们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>) => ....
所断言的内容是正确的:
{{1}}