【问题标题】:React and Typescript Interface "OR" operatorReact 和 Typescript 接口“OR”运算符
【发布时间】:2017-01-09 13:19:54
【问题描述】:

我开始使用 React 和 Typescript 开发应用程序。顺便说一句,我是两件事的新手。

我有一个 react 组件和一些接口,我希望该接口中的每个字段都是必需的,但如果实现了一个特定字段,则其他字段不再需要。

在这种情况下,如果我设置“对象”,我希望不需要实现其他 2 个字段。

interface ITextFieldProps {
    label: string,
    required: boolean,
    object?: any
}

export default class TextField extends React.Component<ITextFieldProps, any> {
    ...
}

希望有人能帮忙,干杯!

编辑

首先我要感谢所有的答案,我真的从他们每个人那里学到了很多。他们帮助我了解我的问题并找到最佳解决方案。

我将传递的对象是元数据,因此它仅在 http 调用后在运行时可用。话虽如此,我还是无法在开发时检查和输入它。

所以我使用 react props spread 解决了我的问题。

(今天typescript不支持,但总有一天会的,嘿嘿https://github.com/Microsoft/TypeScript/issues/2103):

export interface IMetadata {
    label: string,
    required: boolean
}

interface TextFieldProps extends IMetadata {
    ...
}

这样我可以传递一个传播了道具的对象,然后当我调用 label="" 时,它会自动覆盖 {...this.metadata.nome} 中的标签:

<TextField {...this.metadata.nome} label="OverwriteLabel" />

编辑 2

正如丹尼尔所说: 不幸的是,JSX 中的类型检查有一个错误,请参阅:

https://github.com/Microsoft/TypeScript/issues/10171

他们计划在 2.1 中修复它。在那之前,这种技术不适用于 TSX。 (当它修复后,我会回来更新这个答案。)

【问题讨论】:

  • 对象的结构是完全未知的吗?如果是这样,在这种情况下您如何获得labelrequired?我的建议是固定界面并在必要时将对象映射到正确的道具。在我看来,拥有“聪明”的组件可能会根据你扔给它们的东西来做一件事或另一件事只会让事情变得更加复杂。
  • 实际上对象是已知的,它将是一个具有所有这些属性(标签、必需和许多其他)的 json,最好的场景是传递对象,因此开发人员不需要写太多,但如果他没有那个对象并且需要使用该组件,该组件将需要界面中的那些 X 道具...@TomFenech

标签: reactjs typescript interface


【解决方案1】:

你不能这样做,没有办法在接口中表示如果一个属性得到满足,那么另一个属性就不需要了。

你有两个选择(在我看来):

(1) 将所有属性声明为可选:

interface ITextFieldProps {
    label?: string,
    required?: boolean,
    object?: any
}

然后检查该值是否有效:

function isValid(props: ITextFieldProps): boolean {
    return props.label || props.required != null || props.object;
}

(2) 定义一些扩展基础接口的接口:

interface ITextFieldPropsBase {}

interface ITextFieldPropsLabel extends ITextFieldPropsBase {
    label: string;
}

interface ITextFieldPropsBool extends ITextFieldPropsBase {
    value: boolean;
}

interface ITextFieldPropsAny extends ITextFieldPropsBase {
    object: any;
}

class TextField<T extends ITextFieldPropsBase> extends React.Component<T, any> { ... }

这样您每次只能获得其中一个属性。


编辑

@DanielEarwicker 在我的第二个示例中评论了如何将道具传递到组件中。
使用jsx时:

ReactDOM.render(<TextField label="hey" />, document.getElementById("container"));

错误是找不到label,但可以使用ReactDOM.render完成:

ReactDOM.render(React.createElement(TextField, { label: "hey" }), document.getElementById("container"));

第二次编辑

我的第二个建议似乎不适用于这种特定情况。
我从一个稍微不同的场景中得出它:

interface BaseProps {
    name: string;
}

abstract class BaseComponent<T extends BaseProps> extends React.Component<T, {}> {
    render() {
        return <div className={ this.props.name }>{ this.getContent() }</div>
    }

    protected abstract getContent(): JSX.Element;
}

interface DateProps extends BaseProps {
    date: Date;
}

class DateComponent extends BaseComponent<DateProps> {
    protected getContent(): JSX.Element {
        return <div>{ DateComponent.formatDate(this.props.date) }</div>
    }

    private static formatDate(date: Date): string {
        // ...
    }
}

因为我实际上为每个接口都有一个不同的组件类,所以我不需要做任何类型保护、强制转换或任何其他技巧。

@DanielEarwicker 的答案是 OP 描述的场景中的正确答案。

【讨论】:

  • 这是完全可能的,通过联合类型 - 请参阅我的答案。
  • @DanielEarwicker 这也是一个选项,但它与我的第二个选项没有什么不同,并且在某些情况下使用类型保护并不那么舒服(在我看来),例如 react 中的道具。
  • 你说的第一句话是什么意思? :) 组件如何通过您的方法获取属性值?
  • 我的意思是我的答案中的第二个选项与您的解决方案没有太大不同,因为它有点像ITextFieldPropsLabel | ITextFieldPropsBool | ITextFieldPropsAny。你是对的,使用jsx传递道具并不容易,可以使用React.createElement完成。但是你也一样,你如何通过你的解决方案传递jsx 中的道具?
  • 我刚刚发现 TSX 支持中有一个错误,所以我的方法也行不通。但是从 2.1 开始,它应该是正确的 :) 请注意,您的第二种方法是不一样的。 T extends ITextFieldPropsBase 满足不符合要求的事物,例如没有属性的空对象 {}。而我的回答只允许 OP 需要的东西(object 本身,或 both 其他两个属性)。我的问题更多是关于组件内部,当它想要读取this.props 的值时。没有类型保护如何安全地完成?
【解决方案2】:

这是在 TypeScript 中使用联合类型完成的。

interface TextFieldPropsA {
    label: string,
    required: boolean
}

interface TextFieldPropsB {
    object: any
}

以下是定义这些联合的方法:

type TextFieldProps = TextFieldPropsA | TextFieldPropsB;

这字面意思是它必须是第一种或第二种类型(这正是您在问题中所要求的!我们是“或”-ing 两个接口)。

所以:

// no good
const test1: TextFieldProps = { }

// no good, still missing the label
const test2: TextFieldProps = {
    required: true
} 

// this is fine
const test1: TextFieldProps = {
    object: {}
}

(顺便提一下,I 前缀在 TypeScript 中并没有被广泛使用,因为类和函数类型都是接口,所以它实际上并没有告诉你任何有用的信息。)

更新 但是,您需要它来定义 React 道具,因此您可以限制使用 JSX 语法时允许的内容。不幸的是,JSX 中的类型检查有一个错误,请参阅:

https://github.com/Microsoft/TypeScript/issues/10171

他们计划在 2.1 中修复它。在那之前,这种技术不适用于 TSX。 (修复后我会回来更新这个答案!)

【讨论】:

    【解决方案3】:

    听起来这个组件的最小接口总是:

    interface ITextFieldProps {
        label: string;
        required: boolean;
    }
    

    但有时您的对象的属性不止这两个。

    如果是这样,那么您可以将更复杂的对象作为道具传递给组件。只要定义了这两个属性就可以了。


    作为对您的 cmets 的回应,我警告不要让道具过于灵活 - 这样做的代价是使组件变得不必要地复杂。我建议始终传递命名的单个道具,而不是使用更通用的 object 道具。

    我猜你可以使用这样的东西:

    // use the object
    const { label, required } = object;
    
    // ...or use a mixture
    const { label } = object;
    const required = false;
    
    // ...or define everything manually
    const label = "label";
    const required = true;
    
    return <TextField label={label} required={required} />;
    

    这样,最后一行在所有情况下都保持不变,所以 props 接口不需要做任何花哨的事情。

    【讨论】:

    • 实际上对象是已知的,一个json元数据服务器端,具有所有这些属性(标签,必需...),因此开发团队不需要一一编写所有道具,只需传递 obj 和组件安装本身。但是,如果他想在没有 json 和 intelissense 的情况下编写所有组件道具?我不知道我是否清楚,但这似乎是不可能的,我必须像 Nitzan 1) 选项那样做。
    • 所以你是说如果更复杂的对象可用,那么你也想在组件中使用这些属性?
    • 理论上,对象将拥有使组件工作的一切。但是,如果前端开发人员想要更改某些内容,他可以调用类似 &lt;MyComponent object={MyObject} label={MyNewLabel} /&gt; 的名称。在其他情况下,如果他不想使用该对象并写下所有必需的道具,例如&lt;MyComponent label="Label" required="false" /&gt;
    • 我已经编辑了我的答案以展示我推荐使用的方法。
    猜你喜欢
    • 1970-01-01
    • 2015-07-08
    • 1970-01-01
    • 1970-01-01
    • 2020-12-04
    • 2011-10-15
    • 2015-10-16
    • 1970-01-01
    • 2020-10-04
    相关资源
    最近更新 更多