我正在try 构建一个Textfield元素,该元素根据多行属性值使用输入或文本区域.

我正在努力根据MULTLINE的值有条件地分配元素的props 类型.

例如,下面的代码给了我一个错误

'multiline' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

有没有办法做到这一点?

const input = "input";
const textarea = "textarea";

export type TextFieldMultiline<Multiline extends boolean> = {
 multiline: Multiline,
}

export type TextFieldProps<Multiline extends boolean> = 
TextFieldMultiline<Multiline> & (Multiline extends true ? React.ComponentPropsWithRef<typeof textarea> :
React.ComponentPropsWithRef<typeof input>)

export const TextField = ({multiline, ...props}:  TextFieldProps<typeof multiline>) => 
<> {multiline ? <input {...props}/> : <textarea {...props}/>}</>

推荐答案

不要为props类型使用泛型条件类型,而是使用区分联合:

type ReactTextAreaProps = React.ComponentPropsWithRef<"textarea">;
type ReactInputProps = React.ComponentPropsWithRef<"input">;

export type TextFieldMultiline =
    | ({ multiline: true } & ReactTextAreaProps)
    | ({ multiline: false } & ReactInputProps);

然后,该组件如下所示:

export const TextField = (props: TextFieldMultiline) => {
    if (props.multiline) {
        const { multiline: _, ...rest } = props;
        return <textarea {...rest} />;
    }
    const { multiline: _, ...rest } = props;
    return <input {...rest} />;
};

使用示例:

// Valid, allows `textarea`-specific `rows` prop, since `multiline` is `true`:
const a = <TextField multiline={true} rows={10} />;

// Valid, allows `input`-specific `pattern` prop, since `multiline` is `false`:
const b = <TextField multiline={false} pattern="\d+" />;

// INVALID, `multiline` is `true` and `textarea` doesn't have `pattern`:
const c = <TextField multiline={true} pattern="\d+" />;
//                                    ^^^^^^^−−− Error: Type '{ multiline: true; pattern: string; }' is not assignable to type 'IntrinsicAttributes & TextFieldMultiline'. Property 'pattern' does not exist on type...

// INVALID, `multiline` is `false` and `input` doesn't have `rows`:
const d = <TextField multiline={false} rows={10} />;
//                                     ^^^^−−−−− Error: Type '{ multiline: false; rows: number; }' is not assignable to type 'IntrinsicAttributes & TextFieldMultiline'. Property 'rows' does not exist on type...

Playground

从表面上看,您可能认为可以将析构移到参数列表或组件函数的顶层(这基本上是同一件事),但TypeScrip的流分析无法跟踪到,当您这样做(playground)时,multiline为真会缩小rest的类型.有了以上几点,它就可以做到.


我不会这么做,但如果您真的想避免这种重复的析构,您可以使用类型断言like this:

export const TextField = ({ multiline, ...rest }: TextFieldMultiline) => {
    return multiline
        ? <textarea {...rest as ReactTextAreaProps} />
        : <input {...rest as ReactInputProps} />;
};

(Note that there's no need for the fragment wrapper in the question.)

我通常更喜欢没有类型断言的解决方案,因为它们不那么脆弱😊


最后,if你有一个理由,你不能把你的props 类型改变成有区别的联合,你必须坚持使用泛型条件类型,你可以使你的组件函数泛型,以避免multiline类型的循环问题.但在这种情况下,我认为您无法避免实现中的类型断言:

type ReactTextAreaProps = React.ComponentPropsWithRef<"textarea">;
type ReactInputProps = React.ComponentPropsWithRef<"input">;

export type TextFieldMultiline<Multiline extends boolean> = {
    multiline: Multiline;
};

export type TextFieldProps<Multiline extends boolean> =
    TextFieldMultiline<Multiline> &
        (Multiline extends true
            ? ReactTextAreaProps
            : ReactInputProps);

export const TextField = <Multiline extends boolean>({
    multiline,
    ...props
}: TextFieldProps<Multiline>) => {
    return multiline
        ? <textarea {...props as ReactTextAreaProps} />
        : <input {...props as ReactInputProps} />;
};

Playground

Typescript相关问答推荐

具有映射返回类型的通用设置实用程序

如何使用属性作为具有Generics的TypScript类中的类型守护?

node—redis:如何在redis timeSeries上查询argmin?

有没有办法解决这个问题,类型和IntrinsicAttributes类型没有相同的属性?

是否使用非显式名称隔离在对象属性上声明的接口的内部类型?

打印脚本中的动态函数重载

Angular NgModel不更新Typescript

如何将泛型对象作为返回类型添加到类型脚本中

一个打字类型可以实现一个接口吗?

ApexCharts条形图未显示最小值.负值

使用动态输出类型和泛型可重用接口提取对象属性

如何将父属性类型判断传播到子属性

基于泛型的条件接口键

类型推断对React.ForwardRef()的引用参数无效

如何创建内部和外部组件都作为props 传入的包装器组件?

设置不允许相同类型的多个参数的线性规则

递归生成常量树形 struct 的键控类型

来自枚举的<;复选框和组件列表

从联合提取可调用密钥时,未知类型没有调用签名

如何在由函数参数推断的记录类型中具有多个对象字段类型