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

所以,例如……

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

当前回答

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

其他回答

好吧,经过一段时间的尝试和错误,我发现答案并没有像我的用例预期的那样工作。所以为了防止其他人有同样的问题,我想我应该分享我是如何让它工作的。我的界面是这样的:

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

我只是想分享一下,因为如果我遇到了这种奇怪的事情,那么其他人可能也会遇到。

我在为我的案例寻找答案时偶然发现了这个线索(要么是propA,要么是prob,要么都不是)。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;

这个解决方案使所有检查保持原样。也许有人会发现这很有用:)

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

}

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

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

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