打字稿界面 - 可以使“一个或另一个”属性成为必需吗?

时间:2016-06-07 20:03:47

标签: typescript

可能是一个奇怪的问题,但我很好奇是否可以创建一个需要一个属性或另一个属性的接口。

所以,例如......

interface Message {
    text: string;
    attachment: Attachment;
    timestamp?: number;
    // ...etc
}

interface Attachment {...}

在上述情况下,我想确保textattachment存在。

希望这是有道理的。

提前致谢!

编辑:这就是我现在正在做的事情。认为它有点冗长(键入botkit for slack)。

interface Message {
    type?: string;
    channel?: string;
    user?: string;
    text?: string;
    attachments?: Slack.Attachment[];
    ts?: string;
    team?: string;
    event?: string;
    match?: [string, {index: number}, {input: string}];
}

interface AttachmentMessageNoContext extends Message {
    channel: string;
    attachments: Slack.Attachment[];
}

interface TextMessageNoContext extends Message {
    channel: string;
    text: string;
}

8 个答案:

答案 0 :(得分:43)

如果您确实是在“一个或另一个属性”之后,那么可以在扩展类型中使用never

interface MessageBasics {
  timestamp?: number;
  /* more general properties here */
}
interface MessageWithText extends MessageBasics {
  text: string;
  attachment?: never;
}
interface MessageWithAttachment extends MessageBasics {
  text?: never;
  attachment: string;
}
type Message = MessageWithText | MessageWithAttachment;

// ? OK 
let foo: Message = {attachment: 'a'}

// ? OK
let bar: Message = {text: 'b'}

// ❌ ERROR: Type '{ attachment: string; text: string; }' is not assignable to type 'Message'.
let baz: Message = {attachment: 'a', text: 'b'}

Example in Playground

答案 1 :(得分:39)

您可以使用联合类型执行此操作:

import sample

如果你想同时允许文字和附件,你可以写

interface MessageBasics {
  timestamp?: number;
  /* more general properties here */
}
interface MessageWithText extends MessageBasics {
  text: string;
}
interface MessageWithAttachment extends MessageBasics {
  attachment: Attachment;
}
type Message = MessageWithText | MessageWithAttachment;

答案 2 :(得分:5)

感谢@ ryan-cavanaugh让我朝着正确的方向前进。

我有类似的情况,但随后是数组类型。在语法上有点挣扎,所以我把它放在这里供以后参考:

interface BaseRule {
  optionalProp?: number
}

interface RuleA extends BaseRule {
  requiredPropA: string
}

interface RuleB extends BaseRule {
  requiredPropB: string
}

type SpecialRules = Array<RuleA | RuleB>

// or

type SpecialRules = (RuleA | RuleB)[]

// or (in the strict linted project I'm in):

type SpecialRule = RuleA | RuleB
type SpecialRules = SpecialRule[]

更新

请注意,稍后,您可能会在代码中使用声明的变量时收到警告。然后,您可以使用(variable as type)语法。 例如:

const myRules: SpecialRules = [
  {
    optionalProp: 123,
    requiredPropA: 'This object is of type RuleA'
  },
  {
    requiredPropB: 'This object is of type RuleB'
  }
]

myRules.map((rule) => {
  if ((rule as RuleA).requiredPropA) {
    // do stuff
  } else {
    // do other stuff
  }
})

答案 3 :(得分:4)

您可以更深入地使用 @robstarbuck 解决方案创建以下类型:

type Only<T, U> = {
  [P in keyof T]: T[P];
} & {
  [P in keyof U]?: never;
};

type Either<T, U> = Only<T, U> | Only<U, T>;

然后 Message 类型看起来像这样

interface MessageBasics {
  timestamp?: number;
  /* more general properties here */
}
interface MessageWithText extends MessageBasics {
  text: string;
}
interface MessageWithAttachment extends MessageBasics {
  attachment: string;
}
type Message = Either<MessageWithText, MessageWithAttachment>;

使用此解决方案,您可以轻松地在 MessageWithTextMessageWithAttachment 类型中添加更多字段,而无需将其排除在其他类型中。

答案 4 :(得分:2)

好吧,所以经过反复试验和谷歌搜索后,我发现答案没有达到我的用例预期的效果。因此,如果有人遇到同样的问题,我想我应该分享一下我如何使其工作。我的界面是这样的:

export interface MainProps {
  prop1?: string;
  prop2?: string;
  prop3: string;
}

我要寻找的是一个类型定义,它将说我们既不能定义prop1也不能定义prop2。我们可以定义prop1,但不能定义prop2。最后定义了prop2,但没有prop1。这是我发现的解决方案。

interface MainBase {
  prop3: string;
}

interface MainWithProp1 {
  prop1: string;
}

interface MainWithProp2 {
  prop2: string;
}

export type MainProps = MainBase | (MainBase & MainWithProp1) | (MainBase & MainWithProp2);

这很完美,除了一个警告是,当我尝试在另一个文件中引用prop1或prop2时,我一直获取的属性不存在TS错误。这是我如何解决这个问题的方法:

import {MainProps} from 'location/MainProps';

const namedFunction = (props: MainProps) => {
    if('prop1' in props){
      doSomethingWith(props.prop1);
    } else if ('prop2' in props){
      doSomethingWith(props.prop2);
    } else {
      // neither prop1 nor prop2 are defined
    }
 }

只是以为我会分享这一点,因为如果我碰到一点点怪异,那么其他人也可能也是。

答案 5 :(得分:1)

您可以为所需的条件创建一些接口,并以如下所示的类型将其加入:

interface SolidPart {
    name: string;
    surname: string;
    action: 'add' | 'edit' | 'delete';
    id?: number;
}
interface WithId {
    action: 'edit' | 'delete';
    id: number;
}
interface WithoutId {
    action: 'add';
    id?: number;
}

export type Entity = SolidPart & (WithId | WithoutId);

const item: Entity = { // valid
    name: 'John',
    surname: 'Doe',
    action: 'add'
}
const item: Entity = { // not valid, id required for action === 'edit'
    name: 'John',
    surname: 'Doe',
    action: 'edit'
}

答案 6 :(得分:1)

在寻找针对我的案例的答案(propA,propB或都不选择)时,我偶然发现了这个线程。瑞安·藤原(Ryan Fujiwara)的回答几乎做到了,但是我已经失去了一些支票。

我的解决方案:

interface ComponentBase {   
  baseProp: string; 
}

interface ComponentWithPropA extends Base {
  propA: string;
  propB?: never;
}

interface ComponentWithPropB extends Base {
  propB: string;
  propA?: never;
} 

interface ComponentWithoutProps extends Base {
  propA?: never;
  propB?: never;
}

type ComponentProps = ComponentWithPropA | ComponentWithPropB | ComponentWithoutProps;

此解决方案将所有检查保持应有的状态。也许有人会觉得这很有用:)

答案 7 :(得分:0)

有一些不错的Typescript选项,您可以使用https://www.typescriptlang.org/docs/handbook/utility-types.html#omittk

您的问题是:建立一个接口,其中存在“文本”或附件。您可以执行以下操作:

interface AllMessageProperties {
  text: string,
  attachement: string,
}

type Message = Omit<AllMessageProperties, 'text'> | Omit<AllMessageProperties, 'attachement'>;

const messageWithText : Message = {
  text: 'some text'
}

const messageWithAttachement : Message = {
  attachement: 'path-to/attachment'
}

const messageWithTextAndAttachement : Message = {
  text: 'some text',
  attachement: 'path-to/attachment'
}

// results in Typescript error
const messageWithOutTextOrAttachement : Message = {

}
相关问题