Звичайні необов'язкові props дозволяють будь-які комбінації, навіть безглузді:
type ButtonProps = {
href?: string; // посилання
onClick?: () => void; // кнопка
external?: boolean; // має сенс лише з href
};
<Button onClick={save} external /> // компілюється, хоча безглуздо
<Button /> // ні дії, ні посилання
Дискримінований union описує кожен допустимий варіант окремо:
type LinkProps = { as: 'link'; href: string; external?: boolean };
type ActionProps = { as: 'button'; onClick: () => void; disabled?: boolean };
type ButtonProps = (LinkProps | ActionProps) & { children: ReactNode };
function Button(props: ButtonProps) {
if (props.as === 'link') {
return (
<a href={props.href} target={props.external ? '_blank' : undefined}>
{props.children}
</a>
);
}
return (
<button onClick={props.onClick} disabled={props.disabled}>
{props.children}
</button>
);
}
<Button as="link" href="/jobs">Вакансії</Button>
<Button as="button" onClick={save}>Зберегти</Button>
<Button as="button" href="/x">...</Button> // помилка: href не існує для button
Варіант без явного дискримінатора - через never, щоб props взаємно виключали один одного:
type Props =
| { value: string; defaultValue?: never; onChange: (v: string) => void } // керований
| { defaultValue?: string; value?: never; onChange?: never }; // некерований
Передати одночасно value і defaultValue неможливо - типова помилка з полями форм.
Типові застосування:
- керований / некерований компонент;
- компонент стану запиту:
{ status: 'loading' } | { status: 'error'; error: string } | { status: 'success'; data: T }; - модальне вікно з різними наборами кнопок;
- поле форми, де
optionsобов'язкові лише для типуselect.
Пастки:
- деструктуризація в параметрах губить звуження:
function Button({ as, href, onClick })-hrefне існує у варіантіbutton, TypeScript не дасть деструктуризувати. Звужувати треба поprops.as, а деструктуризувати вже всередині гілки; ...restз union дає перетин полів і може «загубити» варіант - передаючи props далі, краще явно перелічувати;- повідомлення про помилки для великих union стають довгими - варто тримати варіантів небагато й давати їм імена.
Докладніше в документації: Звуження типів: дискриміновані union