【问题标题】:TypeScript is not enforcing generic intersection type (React component props)TypeScript 不强制执行通用交集类型(React 组件道具)
【发布时间】:2018-08-08 20:58:51
【问题描述】:

我有一个接受泛型类型参数TChildProps的组件:

type GenericFieldHTMLAttributes =
    | InputHTMLAttributes<HTMLInputElement>
    | SelectHTMLAttributes<HTMLSelectElement>
    | TextareaHTMLAttributes<HTMLTextAreaElement>

interface FieldProps {
    name: string
    label?: string | React.ReactNode
    component?: string | React.ComponentType
    initialValue?: any
    children?: React.ReactNode | FieldRenderFunction
}

class Field<TChildProps = GenericFieldHTMLAttributes> extends React.PureComponent<FieldProps & TChildProps> {
    ...
}

当我使用这个组件时,我希望它能够阻止我传入无法识别的道具,例如:

render() {
    return (
        <Form onSubmit={this.save}>
            <Field foo="test" label="email" name="email" type="email" component={TextField} />
        </Form>
    )
}

令人惊讶的是,上面的代码编译时甚至没有任何警告,尽管foo 属性没有在任何地方定义。我尝试简化示例并得到相同的结果:

class Field<TChildProps = {}> extends React.PureComponent<FieldProps & TChildProps> {
    ...
}

// this still compiles without errors
<Field foo="test" label="email" name="email" type="email" component={TextField} />

TypeScript 是否按照 React.PureComponent 的类型定义在此处正常运行(顺便说一下,我在 React.Component 上对其进行了测试并得到了相同的结果),还是这是一个错误?

【问题讨论】:

    标签: reactjs typescript


    【解决方案1】:

    原答案

    因为&lt;TChildProps = {}&gt;表示如果消费者不给TChilProps赋值,TChildProps的默认值为{}。所以TChildProps 实际上可以是任何东西,如果消费者决定将价值传递给它。这就是为什么 typescript 允许将任何道具传递给Field。您要做的是使用extends 关键字强制TChildProps 的类型。所以试试class Field&lt;TChildProps extends GenericFieldHTMLAttributes&gt;。因此,消费者需要将TChildProps 作为GenericFieldHTMLAttributes 类型传递。你可以在https://www.typescriptlang.org/docs/handbook/generics.html找到更多信息。

    更新

    这是简化的情况(UPDATE2:我倾向于认为这是一个错误或缺少的功能,原因如下)

    下面这个Animal 类接受食物作为泛型反映了React.Component 类接受道具作为泛型。

    class Animal<F> {
      eat(food: F) {
        return;
      }
    }
    

    下面扩展 AnimalLion 类反映了扩展 React.ComponentField

    class Lion<F = {pizza: string}> extends Animal<F> {
    
    }
    

    当消费者消费时,如果他们不指定狮子应该吃什么食物,狮子可以吃任何东西我们应该期望狮子只吃披萨

    const lion1 = new Lion();
    lion1.eat(""); // Argument of  type '""' is not assignable to parameter of type '{pizza: string}'
    

    但是在 tsx 的情况下不会发生这种类型检查。

    解决方法是“将类型和接口都别名为非泛型特化”。

    const TextField = Field as { new (): Field<{value: string}>};
    
    <TextField foo="test" label="email" name="email" type="email" value="asdf"/> // error: Property 'foo' does not exist
    

    这两个链接应该为您提供有关如何使用通用组件的更多信息。

    https://github.com/Microsoft/TypeScript/issues/3960 https://basarat.gitbooks.io/typescript/docs/types/generics.html#generics-in-tsx

    显然他们working on这个。

    2018 年 4 月更新

    这在 Typescript 2.9 中应该是可能的,如 https://github.com/Microsoft/TypeScript/pull/22415 所示。

    【讨论】:

    • 嗯,感谢您的回答和extends 上的信息,但我只是希望GenericFieldHTMLAttributes 成为默认值;我想让消费者可以选择传递一个完全不同的类型(例如,日期选择器组件的道具)。我会和extends一起玩,看看我能不能想出一个解决方案……
    • 现在我想多了,这个答案没有意义。在我的示例中,我没有将任何内容传递给通用 TChildProps 类型,因此它应该只使用默认类型 GenericFieldHTMLAttributes 或者,在我更简单的测试中,只是一个空对象 - 这两个绝对不包括名为foo 的属性。
    • 感谢您提供更多信息,非常有帮助! (实际上我认为您的更新是真正的答案;顶部的部分可以标记为“原始答案”,但这只是一个小建议。)除了在 TSX 中使用泛型不方便的解决方法之外,这里似乎还有一个额外的问题...即 TSX 忽略了泛型的默认参数值,我认为无论如何都应该工作。如果 TypeScript 存储库不存在,我可能会为此提交错误报告。
    • 仅供参考,我认为 basarat.gitbooks.io 页面可能已经重新组织...无论如何,这里是当前特定问题的链接:basarat.gitbooks.io/typescript/docs/types/…
    猜你喜欢
    • 1970-01-01
    • 2019-07-27
    • 1970-01-01
    • 2022-12-11
    • 1970-01-01
    • 2021-09-04
    • 2017-11-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多