За допомогою 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]
Ось посилання на ігровий майданчик TypeScript, де показано обидва в дії.
Застереження 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'
{ [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}