Коротка відповідь
Використовуйте простий props-об’єкт для одного режиму й discriminated union для режимів із різними полями. Розширюйте native props за збереження поведінки елемента. Визначте controlled/uncontrolled стан. TypeScript відхиляє суперечливі виклики; API, CMS і URL усе одно потребують runtime-валідації.
Практичні кроки реалізації
1. Відокремте семантичні режими
Навігація потребує адреси, дія — поведінки. Optional href та onClick допускають жодне чи обидва. Дискримінатор kind задає режим і звужує поля. Навігація має anchor, дія — button; типи не приховують неправильну HTML-семантику.
2. Свідомо зберігайте native-поведінку
ComponentPropsWithoutRef<"button"> дає disabled, type, accessibility і події. Перевизначайте поле, якщо компонент керує значенням. React 19 дозволяє ref як prop функціонального компонента; поради forwardRef звіряйте з версією. Не відкривайте поле, яке компонент не підтримує.
3. Визначте власника стану
Controlled-поле має value і callback, uncontrolled — defaultValue. Не приймайте обидва без правил. ReactNode означає контент, render-function — окремий API. Конкретний доменний компонент часто кращий за polymorphic generic до реальних повторних потреб.
Приклад реалізації
Union забороняє action-props посиланню й вимагає callback дії. never покращує перевірку object literals. Обробники потребують Client Component у App Router, тому модуль має директиву. Приклад не здійснює серверної мутації й не гарантує авторизації callback.
"use client";
import type { ReactNode } from "react";
type ControlProps = { children: ReactNode } & (
| { kind: "link"; href: string; onPress?: never }
| { kind: "action"; onPress: () => void; href?: never }
);
export function Control(props: ControlProps) {
if (props.kind === "link") {
return <a href={props.href}>{props.children}</a>;
}
return (
<button type="button" onClick={props.onPress}>
{props.children}
</button>
);
}
Компроміси та альтернативи
Прості interfaces зрозуміліші за generics для одного режиму. Unions показують несумісність, але важчають від непов’язаних функцій. Розділіть компоненти з різною семантикою; Link і ActionButton можуть мати спільні стилі без спільного API.
Поширені помилки
Уникайте any, casts і широкого string index signature. Не руйнуйте зв’язок дискримінатора перед narrowing. Не передавайте невідомі CMS-поля в DOM. Обов’язкові поля TypeScript не є runtime-перевіркою JSON.
Перевірка перед випуском
- Режими мають потрібні поля.
- Невірні комбінації відхиляються типами.
- Native-семантика й доступність збережені.
- Controlled state має власника.
- Зовнішні дані перевіряються до props.
Поширені запитання
Чи потрібен React.FC усюди?
Ні. Достатньо функції з явним props-параметром. Важливі правильні props і місцевий стиль, не конкретна анотація.
Чи union замінює runtime-валідацію?
Ні. Він обмежує TypeScript-виклики. Парсьте unknown на зовнішній межі й передавайте перевірене доменне значення.
Наступний крок
Знайдіть компонент із суперечливими optional-props, задайте режими й перевірте виклики та HTML. Виносьте стилі до generic-поліморфізму без поточної потреби.
Первинні джерела
Пов’язані матеріали та приклади
Потрібно реалізувати це у вашому продукті?
Перегляньте відповідні приклади продуктів або поділіться контекстом розробки вебзастосунку, розширення Chrome, API чи SaaS.