IHOR.DEVБАЗА_ЗНАНЬ
МЕНЮ

~/blog / Вебстек / typescript-props-patterns-for-react-components.md

// посібник · 2 хв читання

Підходи до типізації props компонентів React у TypeScript

Моделюйте props React через discriminated unions, типи HTML-елементів, явні children і controlled state для перевірки неможливих комбінацій.

Коротка відповідь

Використовуйте простий 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-поліморфізму без поточної потреби.

Первинні джерела

Пов’язані матеріали та приклади

Ігор Харчишин

Незалежний full-stack інженер продуктів, який пише посібники з реалізації розширень браузера та сучасних вебпродуктів на основі першоджерел.

Потрібно реалізувати це у вашому продукті?

Перегляньте відповідні приклади продуктів або поділіться контекстом розробки вебзастосунку, розширення Chrome, API чи SaaS.