Какой тип данных у пропса children в React с TypeScript?
{title}
Правильный тип для children — ReactNode
Самый универсальный и правильный тип для пропса children — это React.ReactNode. Он покрывает все возможные случаи использования. Вот как правильно его применить:
import React, { ReactNode } from 'react';
interface CardProps {
title: string;
children?: ReactNode; // ? означает, что children опционален
}
function Card({ title, children }: CardProps) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-content">
{children}
</div>
</div>
);
}
// Использование:
<Card title="Привет">
Это строка
</Card>
<Card title="Компонент">
<OtherComponent />
</Card>
<Card title="Несколько элементов">
<span>Первый</span>
<span>Второй</span>
</Card>
<Card title="Без children" /> {/* тоже работает */}
Разница между типами
Давайте разберём все типы, чтобы вы понимали, что и когда использовать:
ReactNode— самый широкий тип. Включает:ReactElement, строки, числа,boolean,null,undefined, массивы изReactNode, фрагменты React. Это то, что может быть отрендерено React'ом.ReactElement— только JSX-элементы (созданные черезcreateElementили JSX). Не включает строки, числа,null. Используйте, если нужно гарантировать, что children — именно компонент.JSX.Element— синонимReactElementв большинстве случаев. Но это не React-специфичный тип, а глобальный тип для JSX.ReactChild— устаревший тип, равныйReactElement | string | number. Не включаетnull,undefinedи массивы.ReactFragment— только фрагменты React. Крайне редко используется напрямую.
Почему ReactNode — лучший выбор?
В реальной разработке children может быть чем угодно: текстом (динамическим), другими компонентами, условно отрендеренными элементами (condition && <Component />), массивами из .map(). ReactNode покрывает все эти случаи.
Если вам нужно, чтобы children был обязательным (всегда передавался), уберите знак вопроса:
interface CardProps {
title: string;
children: ReactNode; // обязательно
}
Если вы используете React 18+, импорт React необязателен для JSX, но для типов его всё равно нужно импортировать:
import type { ReactNode } from 'react'; // type-only импорт (экономит бандл)
Дополнительный совет: children как функция (render props)
Иногда children может быть функцией (паттерн render props). Тогда тип будет другим:
interface DataProviderProps {
children: (data: DataType) => ReactNode;
}
function DataProvider({ children }: DataProviderProps) {
const data = { name: 'John' };
return <>{children(data)}</>;
}
В этом случае children — функция, возвращающая ReactNode. Но это уже частный случай, для обычного использования достаточно ReactNode.
Альтернативный подход: использование React.PropsWithChildren
В TypeScript есть встроенная утилита PropsWithChildren<P>, которая автоматически добавляет тип children?: ReactNode к вашим пропсам. Это удобно, когда нужно избежать ручного объявления:
import React, { PropsWithChildren } from 'react';
interface CardProps {
title: string;
}
function Card({ title, children }: PropsWithChildren<CardProps>) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-content">
{children}
</div>
</div>
);
}
Этот подход особенно полезен, когда у вас много компонентов-обёрток и вы не хотите каждый раз писать children?: ReactNode. PropsWithChildren делает код чище и следует принципу DRY.
Обратите внимание: PropsWithChildren делает children опциональным (со знаком ?). Если children обязателен, лучше использовать явное объявление с ReactNode без опциональности.
Ещё один вариант — React.FC<CardProps> (устаревший в React 18+, но всё ещё распространённый). Он автоматически добавляет children в пропсы:
const Card: React.FC<CardProps> = ({ title, children }) => {
// children уже типизирован как ReactNode
};
Однако в современном React (18+) рекомендуется использовать обычные функции вместо React.FC, так как это даёт больше гибкости и меньше магии.