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

所以,例如……

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

当前回答

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

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;

其他回答

简单的“二选一”的例子:

type Props =
  | { factor: Factor; ratings?: never }
  | { ratings: Rating[]; factor?: never }

到目前为止还没有人提到过,但我认为,无论谁偶然看到这一页,都可以考虑使用受歧视的工会。如果我正确理解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);

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

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;

下面是一个非常简单的实用程序类型,我使用它来创建一个新类型,允许多个接口/类型中的一个被称为InterfacesUnion:

export type InterfacesUnion<Interfaces> = BuildUniqueInterfaces<UnionToIntersection<Interfaces>, Interfaces>;

type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;

export type BuildUniqueInterfaces<CompleteInterface, Interfaces> = Interfaces extends object
  ? AssignNever<CompleteInterface, Interfaces>
  : never;

type AssignNever<T, K> = K & {[B in Exclude<keyof T, keyof K>]?: never};

它可以这样使用:

type NewType = InterfacesUnion<AttachmentMessageNoContext | TextMessageNoContext>

它通过接受接口/类型的联合来运行,构建一个包含它们所有属性的单一接口,并返回相同的接口/类型的联合,其中每个接口都包含其他接口拥有而它们没有的附加属性,这些属性被设置为可选的never ([propertyName]?:永远不要)。

有例子/解释的游乐场