Когда использовать JSX.Element, ReactNode и ReactElement в TypeScript?
Разбираемся с типами React в TypeScript
Отличный вопрос! Действительно, эти три типа часто путают. Давайте разложим всё по полочкам.
1. ReactElement — базовый строительный блок
Это самый фундаментальный тип. ReactElement — это объект, описывающий элемент React (то, что вы создаёте с помощью JSX или React.createElement()). У него есть обязательные свойства: type, props и key.
const element: React.ReactElement = <div>Hello</div>;
// Или так:
const element2: React.ReactElement = React.createElement('div', null, 'Hello');
Важно: ReactElement не включает примитивы (строки, числа) и массивы. Только объекты-элементы.
2. JSX.Element — частный случай ReactElement
JSX.Element — это более узкий тип. Он равен ReactElement<any, any> (тип props и type — any). Фактически, это тип, который TypeScript выводит для любого JSX-выражения.
const element: JSX.Element = <MyComponent />;
// Это то же самое, что:
const element2: React.ReactElement = <MyComponent />;
Когда использовать: Как возвращаемый тип из компонента, если вы точно знаете, что возвращаете один элемент. Но не используйте его для children.
3. ReactNode — самый широкий тип
Это объединение (union) всех возможных типов, которые React может отрендерить:
type ReactNode =
| ReactElement
| string
| number
| boolean
| null
| undefined
| ReactNode[]; // Рекурсивно включает массивы
Именно его нужно использовать для пропса children! Потому что children может быть:
- Строкой:
<Card>Привет</Card> - Числом:
<Card>{42}</Card> - Несколькими элементами:
<Card><span/><span/></Card> - null или undefined:
<Card>{condition && <span/>}</Card> - Массивом:
<Card>{[el1, el2]}</Card>
Практические рекомендации
// ✅ Правильно для children
interface CardProps {
children: React.ReactNode;
icon?: React.ReactNode; // И для иконки тоже
}
// ✅ Правильно для возвращаемого значения
function Card({ children, icon }: CardProps): JSX.Element {
return (
<div className="card">
{icon && <span className="icon">{icon}</span>}
<div className="content">{children}</div>
</div>
);
}
// ❌ Неправильно — слишком узко
interface BadProps {
children: JSX.Element; // Не примет строку!
}
Основные правила
- children → всегда
React.ReactNode - Возврат компонента →
JSX.Element(илиReact.ReactElement, если нужна строгость) - Пропсы, которые могут быть элементами (icon, header, footer) →
React.ReactNode - Никогда не используйте
JSX.Elementдля children
Надеюсь, теперь стало понятнее! Если остались вопросы — спрашивайте.
<<Дополнение: когда ReactNode не подходит
Коллега выше всё верно объяснил. Хочу добавить важный нюанс: иногда ReactNode — это слишком широкий тип, и его использование может скрыть ошибки.
Пример проблемы
interface ButtonProps {
children: React.ReactNode;
onClick: () => void;
}
function Button({ children, onClick }: ButtonProps) {
return <button onClick={onClick}>{children}</button>;
}
// ❌ Ошибка не отловится:
<Button onClick={() => {}}>
{someObject} // ReactNode разрешает объекты, но React их не отрендерит!
</Button>
Когда использовать более строгие типы
- ReactElement — если children обязательно должен быть React-элементом (например, для компонента-обёртки, который клонирует children)
- ReactChild — если нужно разрешить только элементы, строки и числа (без массивов и null)
- Кастомный тип — если у вас специфические требования
Пример строгой типизации
// Только один элемент (не массив)
interface SingleChildProps {
children: React.ReactElement;
}
// Только текст
interface TextOnlyProps {
children: string;
}
В 90% случаев ReactNode — правильный выбор. Но если вам нужна строгость — не бойтесь сужать тип.