[英]Correctly typing a HOC component that returns a component with forwardRef
I am trying to make a HOC which returns a component with forwardRef but am not sure how to type it Here is the code我正在尝试制作一个 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);
But am getting two errors one on the InputComponent
但是在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'
and the other is on Input.Number
另一个是在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<...>>'.
Here is a link to a codesandbox if someone wants to try it out: https://codesandbox.io/s/dazzling-shape-rwmmg?file=/src/Input.tsx:0-959如果有人想尝试,这里有一个代码沙盒的链接: https ://codesandbox.io/s/dazzling-shape-rwmmg?file=/src/Input.tsx:0-959
The Input.Number
error is the easier one to understand. Input.Number
错误更容易理解。 You are trying to export an object which is a component and has a property Number
which is a different component.您正在尝试导出一个对象,该对象是一个组件并且具有一个不同的组件属性Number
。 The error is telling you that you can't set an arbitrary property like Number
on a component, which kinda makes sense (It is doable, but complicated).错误告诉您不能在组件上设置像Number
这样的任意属性,这有点道理(这是可行的,但很复杂)。 I recommend putting both the Base
and the Number
at the same level rather than having one as a property of the other.我建议将Base
和Number
放在同一级别,而不是将一个作为另一个的属性。
const Input = {
Base: withInput(InputBaseComponent),
Number: withInput(NumberInputBaseComponent)
}
Now Input
is just an object with two different components as properties.现在Input
只是一个具有两个不同组件作为属性的对象。
You could also export the various instances individually, and group them together when you import by doing您还可以单独导出各种实例,并在导入时将它们组合在一起
import * as Input from `./Input`
First of all, I think that your InputProps
interface isn't doing what you intended for it to do.首先,我认为您的InputProps
接口没有按照您的意图执行。
export interface InputProps<T extends Omit<T, Omitted>> {
startIcon?: React.ReactElement;
endIcon?: React.ReactElement;
}
This interface says that InputProps
is an object with an optional startIcon
and endIcon
.这个接口表示InputProps
是一个带有可选startIcon
和endIcon
的对象。 That's it.就是这样。 T
is never used and doesn't matter. T
从来没有被使用过,也没有关系。 No props of T
are included in InputProps
. InputProps
不包含T
道具。 When you spread InputProps
into { startIcon, endIcon, ...props }
, the only prop that's left in ...props
is children
.当您将InputProps
传播到{ startIcon, endIcon, ...props }
中唯一剩下的...props
是children
。 So of course this is going to cause an error when we want ...props
to include all of the props P
of InputComponent
.因此,当我们希望...props
包含InputComponent
所有 props P
,这当然会导致错误。
I think what you meant to say here was that InputProps<T>
contains those two icons AND all of T
, except for the omitted properties.我想你在这里想说的是InputProps<T>
包含这两个图标和所有T
,除了省略的属性。
export type InputProps<T> = Omit<T, Omitted> & {
startIcon?: React.ReactElement;
endIcon?: React.ReactElement;
}
This is better because now we've got some actual props in ...props
.这更好,因为现在我们在...props
有一些实际的...props
。 Hovering over it shows悬停在它显示
Pick<React.PropsWithChildren<InputProps<P>>, "children" | Exclude<Exclude<keyof P, Omitted>, "startIcon" | "endIcon">>
So basically we've got everything in P
plus children
minus variant
, size
, startIcon
, and endIcon
.所以基本上我们已经得到了P
所有内容加上children
减去variant
、 size
、 startIcon
和endIcon
。 Is this subset assignable to P
?这个子集可以分配给P
吗? Maybe, but not always, and that's what the error is telling you.也许,但并非总是如此,这就是错误告诉你的。
It's common for typescript to have a hard time understanding that the pieces make up the whole when using {...rest}
syntax.当使用{...rest}
语法时,打字稿很难理解这些部分构成了整体,这是很常见的。 I often have to assert as P
in my HOCs.我经常不得不在我的 HOC 中声明as P
Regardless of everything in the next few paragraphs, you're still going to end up doing that here:无论接下来几段中的所有内容如何,您最终仍将在此处执行此操作:
<InputComponent {...props as P} ref={ref} />
Is is really ok to say that ...props
is P
?真的可以说...props
是P
吗? When we use as
, we are basically telling typescript to hush because we know more than it does.当我们使用as
,我们基本上是在告诉 typescript 安静,因为我们知道的比它多。 So we want to make sure that we aren't giving it bad information.因此,我们希望确保不会向其提供不良信息。
When will {...props} not be assignable to P
?什么时候 {...props}不能分配给P
? How can we prevent those instances from occurring?我们如何防止这些情况发生?
There is some cause for concern here due to the fact that we Omit
variant
and size
from the props of the inner component, so ...props
(which we want to be P
) is known by typescript to not contain either of these properties.由于我们从内部组件的 props 中Omit
variant
和size
,所以这里有一些值得关注的原因,所以...props
(我们希望它是P
)被打字稿知道不包含这些属性中的任何一个。 If these properties exist on type P
and are required, then ...props
cannot be P
.如果这些属性存在于type P
并且是必需的,那么...props
不能是P
。 The same is true if P
includes required properties startIcon
and endIcon
, since we pulled those out with the spread.如果P
包含必需的属性startIcon
和endIcon
,因为我们将它们与价差一起取出。
For variant
and size
, I don't really see the point to omitting them at all.对于variant
和size
,我根本看不出省略它们的意义。 You aren't setting any default values for them, so why not let them be passed through?您没有为它们设置任何默认值,那么为什么不让它们通过呢?
But as a general idea, and for startIcon
and endIcon
, we need to refine our P extends
such that if P
has these properties, they must be optional.但是作为一般的想法,对于startIcon
和endIcon
,我们需要改进我们的P extends
这样如果P
具有这些属性,它们必须是可选的。 We also want to make sure that our InputComponent
can accept the ref
type which we are passing it.我们还想确保我们的InputComponent
可以接受我们传递给它的ref
类型。
You can be more confident that what we are asserting with as
is accurate if you refine the types like this:你可以更有信心,我们与声称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.