Typescript Интерфейс - возможно ли выполнить "один или другой" свойства?

Возможно, странный вопрос, но мне любопытно, можно ли создать интерфейс, где требуется одно свойство или другое.

Итак, например...

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

interface Attachment {...}

В приведенном выше случае я хотел бы убедиться, что существует либо text, либо attachment.

Надеюсь, это имеет смысл.

Спасибо заранее!


Изменить: Вот как я это делаю прямо сейчас. Думал, что это было немного многословно (набрав ботки для слабины).

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

Ответ 1

Для этого можно использовать тип объединения:

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

Ответ 2

Спасибо @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[]

Update:

Обратите внимание, что в дальнейшем вы все равно можете получать предупреждения при использовании объявленной переменной в своем коде. Затем вы можете использовать синтаксис (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
  }
})

Ответ 3

Вы можете создать несколько интерфейсов для требуемых условий и объединить их в виде, подобном следующему:

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

Ответ 4

Есть несколько классных опций 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 = {

}