интерфейс typescript требует наличия одного из двух свойств

Я пытаюсь создать интерфейс, который мог бы

export interface MenuItem {
  title: string;
  component?: any;
  click?: any;
  icon: string;
}
  1. есть ли способ обязать component или click установить
  2. есть ли способ потребовать, чтобы оба свойства не могли быть установлены?

5 ответов


Не с одним интерфейсом, так как типы не имеют условной логики и не могут зависеть друг от друга, но вы можете, разделив интерфейсы:

export interface BaseMenuItem {
  title: string;
  icon: string;
}

export interface ComponentMenuItem extends BaseMenuItem {
  component: any;
}

export interface ClickMenuItem extends BaseMenuItem {
    click: any;
}

export type MenuItem = ComponentMenuItem | ClickMenuItem;

С помощью Exclude тип, который был добавлен в TypeScript 2.8, обобщаемый способ потребовать хотя бы одного из набора свойств:

type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
    Pick<T, Exclude<keyof T, Keys>>
    & {
        [K in Keys]-?: Required<Pick<T, K>>
    }[Keys]

и частичный, но не абсолютный способ потребовать, чтобы был предоставлен только один:

type RequireOnlyOne<T, Keys extends keyof T = keyof T> =
    Pick<T, Exclude<keyof T, Keys>>
    & {
        [K in Keys]-?:
            Required<Pick<T, K>>
            & Partial<Record<Exclude<Keys, K>, undefined>>
    }[Keys]

вот ссылка TypeScript playground, показывающая оба в действии.

нюанс с RequireOnlyOne это то, что TypeScript не всегда знает во время компиляции каждое свойство это будет существовать во время выполнения. Так что, очевидно,RequireOnlyOne не могу ничего сделать, чтобы предотвратить дополнительные свойства, о которых он не знает. Я привел пример того, как RequireOnlyOne может пропустить вещи в конце ссылки playground.

краткий обзор того, как это работает, используя следующий пример:

interface MenuItem {
  title: string;
  component?: any;
  click?: any;
  icon: string;
}

type ClickOrComponent = RequireAtLeastOne<MenuItem, 'click' | 'component'>
  1. Pick<T, Exclude<keyof T, Keys>> С RequireAtLeastOne становится { title: string, icon: string}, которые являются неизменными свойствами ключей, не включенных в 'click' | 'component'

  2. { [K in Keys]-?: Required<Pick<T, K>>}[Keys] С RequireAtLeastOne становится

    { 
       component: Required < { component?: any } >, 
       click: Required<{ click?: any }> 
    }[Keys]
    

    что будет

    {
        component: { component: any },
        click: { click: any }
    }['component' | 'click']
    

    , который, наконец, становится

    {component: any} | {click: any}
    
  3. пересечение 1 и 2 выше

    { title: string, icon: string} & ({component: any} | {click: any})
    

    упрощается до

    { title: string, icon: string, component: any} | { title: string, icon: string, click: any}
    

альтернативой без нескольких интерфейсов является

export type MenuItem = {
  title: string;
  component: any;
  icon: string;
} | {
  title: string;
  click: any;
  icon: string;
};

Я задал аналогичный вопрос в как создать частичный-как это требует, чтобы одно свойство было установлено


Я закончил делать:

export interface MenuItem {
  title: string;
  icon: string;
}

export interface MenuItemComponent   extends MenuItem{
  component: any;
}

export interface MenuItemClick extends MenuItem{
  click: any;
}

затем я использовал:

 appMenuItems: Array<MenuItemComponent|MenuItemClick>;

но надеялся, что есть способ смоделировать его с помощью одного интерфейса.


больше вопрос, чем ответ, (по аналогии с "Хуан Мендес") вы могли бы это сделать?

export type MenuItemCommon = {
  title: string;
  icon: string;
}

export type Component = {
    component: int;
}

export type Click = {
    click: string;
}

export type MenuItem = MenuItemCommon & (Component | Click)

my ide, (intellij/webstorm) позволяет мне объявлять тип без ошибок, но жалуется, если я ссылаюсь на компонент или щелчок. Что, я думаю, правильно, потому что это не гарантировано. поэтому я должен ссылаться на него как

menuItem['component']

это не мешает мне делать

menuItem:MenuItem = {title:"t",icon:"i",component:1,click:"c"}

т. е. укажите оба свойства