简体   繁体   English

正确键入返回带有 forwardRef 的组件的 HOC 组件

[英]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

Property 'Number' does not exist属性“编号”不存在

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.我建议将BaseNumber放在同一级别,而不是将一个作为另一个的属性。

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`

Assigning ...props to 'P'将 ...props 分配给 'P'

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是一个带有可选startIconendIcon的对象。 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 }中唯一剩下的...propschildren 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减去variantsizestartIconendIcon 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} />

Type Safety类型安全

Is is really ok to say that ...props is P ?真的可以说...propsP吗? 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 variantsize ,所以这里有一些值得关注的原因,所以...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包含必需的属性startIconendIcon ,因为我们将它们与价差一起取出。

For variant and size , I don't really see the point to omitting them at all.对于variantsize ,我根本看不出省略它们的意义。 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.但是作为一般的想法,对于startIconendIcon ,我们需要改进我们的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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM