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

dsi*_*ord 34 typescript

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

所以,例如......

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

interface Attachment {...}
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,我想确定是存在text还是attachment存在.

希望这是有道理的.

提前致谢!


编辑:这就是我现在正在做的事情.认为它有点冗长(输入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;
}
Run Code Online (Sandbox Code Playgroud)

rob*_*uck 90

如果您真正追求“一个属性或另一个”而不是两者,您可以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'}
Run Code Online (Sandbox Code Playgroud)

游乐场中的示例

  • 这是最好的一个,它在分配 2 个属性的情况下尖叫,并且与之前的答案不同,启用了自动完成功能 (6认同)
  • @golopot,阅读您的评论后,这应该是您想要的解决方案。 (3认同)

Rya*_*ugh 49

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

interface MessageBasics {
  timestamp?: number;
  /* more general properties here */
}
interface MessageWithText extends MessageBasics {
  text: string;
}
interface MessageWithAttachment extends MessageBasics {
  attachment: Attachment;
}
type Message = MessageWithText | MessageWithAttachment;
Run Code Online (Sandbox Code Playgroud)

如果你想同时允许文本和附件,你会写

type Message = MessageWithText | MessageWithAttachment | (MessageWithText & MessageWithAttachment);
Run Code Online (Sandbox Code Playgroud)

  • 答案没有按预期工作,输入 `Message` 接受 `{text: 'a',attachment: 'b'}` https://www.typescriptlang.org/play/index.html?ssl=13&ssc=1&pln= 13&pc=48#code/JYOwLgpgTgZghgYwgAgLIQM4bgcwgITg2AQ2QG8BYAKGWTGAFtMw5GAHAfgC5kQBXRgCNoAbhp0A9ACpkjAPZQUEENDgAbZOyjz20BpmQALaCmmSaAXxqhIsRCnRZceAOrAwrgCoQAHmGQ-S BAAEzInbDxCYliKCXog3gwwKFAccWpraltoeCQ0TEi3DyMAQTBWBCNmcED-CFDwwpdokjIqWmQ4CsRqhrAklLSMrLAATz0C5zxkAF4povdPH39kAB8FlyWynqqasAyadQgamHl5XgiXOYpIf14AcjgHgBou3b7wR+fLI一个 (8认同)
  • @罗伯托但这并不排除!这两者都接受。 (7认同)
  • 最后那个`| (MessageWithText & MessageWithAttachment)` 不执行任何操作 - 如果没有它,它的行为完全相同。 (4认同)
  • 但是这不允许他留言带有文字和附件 (3认同)

Dar*_*rek 13

我在寻找我的案例的答案时偶然发现了这个线程(propA、propB 或都没有)。Ryan Fujiwara 的回答几乎成功了,但我因此丢失了一些支票。

我的解决方案:

interface Base {   
  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;
Run Code Online (Sandbox Code Playgroud)

该解决方案使所有检查保持应有的状态。也许有人会发现这很有用:)


Sha*_*hah 11

简单的“需要两个之一”示例:

type Props =
  | { factor: Factor; ratings?: never }
  | { ratings: Rating[]; factor?: never }
Run Code Online (Sandbox Code Playgroud)


Fab*_*lev 11

我进一步压缩了 @Voskanyan David 的解决方案,得到了我个人认为非常简洁的解决方案:

您仍然需要在某处定义一次 Only<> 和 Either<> 一次

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>
Run Code Online (Sandbox Code Playgroud)

之后,就可以在没有任何中间接口/类型的情况下将其定义为一件事:

type Message = {
    type?: string;
    channel?: string;
    user?: string;
    // ...etc
} & Either<{message: string}, {attachment: Attachment}>
Run Code Online (Sandbox Code Playgroud)


pub*_*orn 6

谢谢@ 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[]
Run Code Online (Sandbox Code Playgroud)

更新:

请注意,稍后,当您在代码中使用声明的变量时,可能仍会收到警告.然后,您可以使用(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
  }
})
Run Code Online (Sandbox Code Playgroud)

  • 只是一个快速的说明,让你知道有一种类型安全,intellisense友好的方式来编写你的`.map()`代码.使用带有`in`运算符的type-guard.TypeScript会进行正确的类型推断,等等.所以你的条件是`if(规则中的'requiredPropA'){/*在这里,TS知道规则的类型是<RuleA>*/} (6认同)

cud*_*ter 6

您可以为所需条件创建几个接口,并将它们加入如下类型:

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'
}
Run Code Online (Sandbox Code Playgroud)


Vos*_*vid 6

您可以更深入地使用 @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>;
Run Code Online (Sandbox Code Playgroud)

然后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>;
Run Code Online (Sandbox Code Playgroud)

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

  • 很棒的通用解决方案。如果您的接口的一般属性在键上相同,但具有某些不同的值,则可以使用“Only”的此修改版本:“type Only&lt;T, U&gt; = { [P in keyof T]: T[P] } &amp; 省略&lt;{ [P in keyof U]?: 从不 }, keyof T&gt;` (8认同)
  • 这是一个拯救生命的解决方案。给你的道具。我很惊讶 Typescript 没有选项可以更轻松地做到这一点。 (2认同)

Edv*_*son 6

你也可以这样。

abstract class BaseMessage {
  timestamp?: number;
  /* more general properties here */
  constructor(timestamp?: number) {
    this.timestamp = timestamp;
    /* etc. for other general properties */
  }
}
interface IMessageWithText extends BaseMessage {
  text: string;
  attachment?: never;
}
interface IMessageWithAttachment extends BaseMessage {
  text?: never;
  attachment: string;
}
type Message = IMessageWithText | IMessageWithAttachment;
Run Code Online (Sandbox Code Playgroud)