Интерфейс typescript требует наличия одного из двух свойств
Я пытаюсь создать интерфейс, который может иметь
export interface MenuItem {
title: string;
component?: any;
click?: any;
icon: string;
}
- Можно ли установить
component
или click
способ
- Есть ли способ требовать, чтобы оба свойства не могли быть установлены?
Ответы
Ответ 1
Не с одним интерфейсом, так как типы не имеют условной логики и не могут зависеть друг от друга, но вы можете разделить интерфейсы:
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;
Ответ 2
С помощью типа 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>> & Partial<Pick<T, Exclude<Keys, 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]
=
Pick>
& {
[K in Keys%5D-?: Required> & Partial>>
}[Keys]
interface MenuItem {
title: string;
component?: number;
click?: number;
icon: string;
}
type ClickOrComponent = RequireAtLeastOne
const withComponent: ClickOrComponent = {
title: "test",
component: 52,
icon: "icon"
}
const withClick: ClickOrComponent = {
title: "test",
click: 54,
icon: "icon"
}
const errorWithNeither: ClickOrComponent = {
title: "test",
icon: "icon"
}
const noErrorWithBoth: ClickOrComponent = {
title: "test",
click: 54,
component: 24,
icon: "icon"
}
const errorWithBothWhenOneHasWrongType: ClickOrComponent = {
title: "test",
click: 54,
component: "should be number here",
icon: "icon"
}
type RequireOnlyOne =
Pick>
& { [K in Keys%5D-?:
Required>
& Partial, undefined>>
}[Keys]
type OnlyOneClickOrComponent = RequireOnlyOne
const noErrorWithOnlyOne: OnlyOneClickOrComponent = {
title: "test",
click: 534,
icon: "icon"
}
const errorWithBoth: OnlyOneClickOrComponent = {
title: "test",
click: 534,
component: 53,
icon: "icon"
}
//This interface will be used to trick OnlyOneClickAtComponent into allowing an object with both
interface ClickMenuItem {
title: string;
click: number;
icon: string;
}
const hasBoth = {
title: "test",
click: 54,
component: 24,
icon: "icon"
}
//No error because excess properties are only disallowed when directly assigning an object literal
const temp: ClickMenuItem = hasBoth
//No error despite temp actually having both because TS only knows that temp is a ClickMenuItem
const trickIntoAllowingBoth: OnlyOneClickOrComponent = temp
rel="nofollow noreferrer">Вот ссылка на игровую площадку TypeScript, показывающая оба в действии.
RequireOnlyOne
с RequireOnlyOne
заключается в том, что TypeScript не всегда знает во время компиляции каждое свойство, которое будет существовать во время выполнения. Очевидно, что RequireOnlyOne
не может ничего сделать, чтобы предотвратить дополнительные свойства, о которых он не знает. Я привел пример того, как RequireOnlyOne
может пропустить вещи в конце ссылки на игровую площадку.
Краткий обзор того, как это работает, на следующем примере:
interface MenuItem {
title: string;
component?: number;
click?: number;
icon: string;
}
type ClickOrComponent = RequireAtLeastOne<MenuItem, 'click' | 'component'>
-
Pick<T, Exclude<keyof T, Keys>>
из RequireAtLeastOne
становится { title: string, icon: string}
, которые являются неизменными свойствами ключей, не включенных в 'click' | 'component'
'click' | 'component'
-
{ [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>> }[Keys]
из RequireAtLeastOne
становится
{
component: Required<{ component?: number }> & { click?: number },
click: Required<{ click?: number }> & { component?: number }
}[Keys]
Который становится
{
component: { component: number, click?: number },
click: { click: number, component?: number }
}['component' | 'click']
Который, наконец, становится
{component: number, click?: number} | {click: number, component?: number}
-
Пересечение шагов 1 и 2 выше
{ title: string, icon: string}
&
({component: number, click?: number} | {click: number, component?: number})
упрощает до
{ title: string, icon: string, component: number, click?: number}
| { title: string, icon: string, click: number, component?: number}
Ответ 3
Альтернативой без нескольких интерфейсов является
export type MenuItem = {
title: string;
component: any;
icon: string;
} | {
title: string;
click: any;
icon: string;
};
const item: MenuItem[] = [
{ title: "", icon: "", component: {} },
{ title: "", icon: "", click: "" },
// Shouldn't this error out because it passing a property that is not defined
{ title: "", icon: "", click: "", component: {} },
// Does error out :)
{ title: "", icon: "" }
];
Я задал аналогичный вопрос о том, как создать Partial-like, для которого требуется установить одно свойство
Вышеупомянутое может быть упрощено, но его может быть или не быть легче читать
export type MenuItem = {
title: string;
icon: string;
} & (
{component: any} | {click: string}
)
Обратите внимание, что ни одно из них не позволяет вам добавлять оба, потому что TypeScript разрешает дополнительные свойства объектов, которые используют AND/OR См. Https://github.com/Microsoft/TypeScript/issues/15447
Ответ 4
Я закончил тем, что сделал:
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>;
Но надеялся, что найдется способ смоделировать его с помощью единого интерфейса.
Ответ 5
Мне нравится использовать Pick
вместе с базовым типом, который включает в себя все свойства, чтобы установить эти виды условных требований.
interface MenuItemProps {
title: string;
component: any;
click: any;
icon: string;
}
export interface MenuItem =
Pick<MenuItemProps, "title" | "icon" | "component"> |
Pick<MenuItemProps, "title" | "icon" | "click">
Это чисто, а также гибко. Вы можете произвольно усложнить свои требования, утверждая, что "требуется либо все свойства, только эти два свойства, либо только это одно свойство" и т.д., Сохраняя при этом вашу декларацию простой и читаемой.
Ответ 6
Не могли бы вы ответить на этот вопрос, если бы не ответ? (Силианс "Хуан Мендес").
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"}
т.е. указать оба свойства