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

所以,例如……

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

当前回答

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

}

其他回答

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

}

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

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

不使用扩展

使用这里描述的XOR: https://stackoverflow.com/a/53229567/8954109

// Create a type that requires properties `a` and `z`, and one of `b` or `c`
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;

interface Az {
  a: number;
  z: number;
}

interface B {
  b: number;
}

interface C {
  c: number;
}

type XorBC = XOR<B, C>;
type AndAzXorBC = Az & XorBC;
type MyData = AndAzXorBC;

const ok1: MyData = { a: 0, z: 1, b: 2 };
const ok2: MyData = { a: 0, z: 1, c: 2 };
const badBothBC: MyData = {
  a: 0, z: 1,
  b: 2,
  c: 3
};
const badNoBC: MyData = { a: 0, z: 1 };
const badNoZ: MyData = { a: 0, b: 2 };

对无效类型产生以下错误:

src/App.tsx:30:7 - error TS2322: Type '{ a: number; z: number; b: number; c: number; }' is not assignable to type 'AndAzXorBC'.
  Type '{ a: number; z: number; b: number; c: number; }' is not assignable to type 'Az & Without<C, B> & B'.
    Type '{ a: number; z: number; b: number; c: number; }' is not assignable to type 'Without<C, B>'.
      Types of property 'c' are incompatible.
        Type 'number' is not assignable to type 'undefined'.

30 const badBothBC: MyData = {
src/App.tsx:35:7 - error TS2322: Type '{ a: number; z: number; }' is not assignable to type 'AndAzXorBC'.
  Type '{ a: number; z: number; }' is not assignable to type 'Az & Without<C, B> & B'.
    Property 'b' is missing in type '{ a: number; z: number; }' but required in type 'B'.

35 const badNoBC: MyData = { a: 0, z: 1 };
         ~~~~~~~

  src/App.tsx:17:3
    17   b: number;
         ~
    'b' is declared here.
src/App.tsx:36:7 - error TS2322: Type '{ a: number; b: number; }' is not assignable to type 'AndAzXorBC'.
  Type '{ a: number; b: number; }' is not assignable to type 'Az & Without<C, B> & B'.
    Property 'z' is missing in type '{ a: number; b: number; }' but required in type 'Az'.

36 const badNoZ: MyData = { a: 0, b: 2 };
         ~~~~~~

  src/App.tsx:13:3
    13   z: number;
         ~
    'z' is declared here.

你可以更深入地使用@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>;

然后消息类型看起来像这样

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

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