интерфейс typescript требует наличия одного из двух свойств
Я пытаюсь создать интерфейс, который мог бы
export interface MenuItem {
title: string;
component?: any;
click?: any;
icon: string;
}
- есть ли способ обязать
component
илиclick
установить - есть ли способ потребовать, чтобы оба свойства не могли быть установлены?
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'>
Pick<T, Exclude<keyof T, Keys>>
СRequireAtLeastOne
становится{ title: string, icon: string}
, которые являются неизменными свойствами ключей, не включенных в'click' | 'component'
-
{ [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}
-
пересечение 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"}
т. е. укажите оба свойства