这可能是一个奇怪的问题,但我很好奇是否有可能创建一个需要其中一个属性的接口。

所以,例如……

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

interface Attachment {...}

在上面的情况下,我想确保文本或附件都存在。


我现在就是这么做的。我觉得它有点啰嗦(为slack输入botkit)。

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;
}

当前回答

谢谢@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[]

更新:

请注意,稍后在代码中使用声明的变量时,可能仍然会收到警告。然后可以使用(变量作为类型)语法。 例子:

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
  }
})

其他回答

有一些很酷的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 = {

}

到目前为止还没有人提到过,但我认为,无论谁偶然看到这一页,都可以考虑使用受歧视的工会。如果我正确理解OP代码的意图,那么它可能会像这样转换。

interface Attachment {}

interface MessageBase {
    type?: string;
    user?: string;
    ts?: string;
    team?: string;
    event?: string;
    match?: [string, {index: number}, {input: string}];
}

interface AttachmentMessageNoContext extends MessageBase {
    kind: 'withAttachments',
    channel: string;
    attachments: Attachment[];
}

interface TextMessageNoContext extends MessageBase {
    kind: 'justText', 
    channel: string;
    text: string;
}

type Message = TextMessageNoContext | AttachmentMessageNoContext

const textMessage: Message = {
  kind: 'justText',
  channel: 'foo',
  text: "whats up???" 
}

const messageWithAttachment: Message = {
  kind: 'withAttachments',
  channel: 'foo',
  attachments: []
}

现在Message接口需要附件或文本,这取决于kind属性。

你可以使用联合类型来做到这一点:

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

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

type Message = MessageWithText | MessageWithAttachment | (MessageWithText & MessageWithAttachment);

我进一步压缩了@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>

之后,可以在没有任何中间接口/类型的情况下定义它:

type Message {
    type?: string;
    channel?: string;
    user?: string;
    // ...etc
} & Either<{message: string}, {attachment: Attachment}>

还可以为通用属性使用抽象类,而不是接口,以防止某人意外实现该接口。

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;