React TypeScript: как решить проблему с кастомным компонентом

Короткий ответ: если в React + TypeScript ломается кастомный компонент, почти всегда проблема в props. Нужно явно описать, какие свойства компонент принимает, добавить children, если внутри есть вложенный контент, и расширить HTML-атрибуты, если компонент должен вести себя как обычная кнопка, ссылка или input

Типичная ошибка выглядит так:

Property 'children' does not exist on type ...

или:

Type '{ onClick: ... }' is not assignable to type ...

Базовый компонент с props

Плохо:

export function Badge(props) {
  return <span>{props.label}</span>;
}

В TypeScript лучше так:

type BadgeProps = {
  label: string;
  tone?: "success" | "warning" | "danger";
};

export function Badge({ label, tone = "success" }: BadgeProps) {
  return <span className={`badge badge-${tone}`}>{label}</span>;
}

Теперь TypeScript знает, что label обязателен, а tone может быть только одним из трех значений

Использование:

<Badge label="Опубликовано" tone="success" />

Если написать tone="blue", TypeScript поймает ошибку

Если нужен children

Если компонент принимает вложенный контент, добавьте children:

import type { ReactNode } from "react";

type CardProps = {
  title: string;
  children: ReactNode;
};

export function Card({ title, children }: CardProps) {
  return (
    <section className="card">
      <h2>{title}</h2>
      <div>{children}</div>
    </section>
  );
}

Использование:

<Card title="Профиль">
  <p>Данные пользователя</p>
</Card>

Без children TypeScript будет честно ругаться: вы передаете вложенный JSX, а тип компонента такого свойства не обещает

Если компонент должен быть как button

Частая проблема: кастомная кнопка не принимает onClick, disabled, type и другие стандартные атрибуты

Решение:

import type { ButtonHTMLAttributes, ReactNode } from "react";

type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
  variant?: "primary" | "secondary";
  children: ReactNode;
};

export function Button({
  variant = "primary",
  children,
  className,
  ...rest
}: ButtonProps) {
  return (
    <button className={`btn btn-${variant} ${className ?? ""}`} {...rest}>
      {children}
    </button>
  );
}

Теперь работает:

<Button type="button" disabled={false} onClick={() => alert("click")}>
  Сохранить
</Button>

Ключевая идея: если ваш компонент оборачивает настоящий HTML-элемент, типизируйте стандартные атрибуты этого элемента

Если нужен ref

Обычный prop ref не проходит в функциональный компонент автоматически. Для ref используйте forwardRef:

import { forwardRef } from "react";
import type { ButtonHTMLAttributes } from "react";

type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
  variant?: "primary" | "secondary";
};

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ variant = "primary", className, ...rest }, ref) => {
    return (
      <button
        ref={ref}
        className={`btn btn-${variant} ${className ?? ""}`}
        {...rest}
      />
    );
  }
);

Button.displayName = "Button";

Это нужно для форм, фокуса, UI-библиотек и компонентов, которые должны отдавать ссылку на DOM-элемент

Частые ошибки

Первая ошибка — писать props как any. Ошибка исчезает, но польза TypeScript тоже исчезает

Вторая ошибка — забыть children. Если компонент используется как контейнер, children должен быть в типе

Третья ошибка — не передать ...rest в настоящий HTML-элемент. Тогда onClick формально принимается, но не работает в DOM

Четвертая ошибка — использовать React.FC только ради children. Сейчас чаще проще явно описать children: ReactNode, чтобы тип был виден и контролируем

Самопроверка

Создайте кастомный Button, передайте в него onClick, disabled, type="submit" и вложенный текст. Если TypeScript не ругается, а кнопка работает в браузере, базовая проблема решена

Потом намеренно передайте неправильный variant="blue". Если TypeScript ловит ошибку, ваши props действительно типизированы, а не превращены в any

Что почитать дальше по TypeScript

Если нужен общий маршрут по теме, откройте рубрику TypeScript. Для соседних задач пригодятся эти разборы:

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x