Когда использовать JSX.Element, ReactNode и ReactElement в TypeScript с React?
Краткая шпаргалка: что есть что
Давайте сразу разложим по полочкам, а потом разберём на примерах.
- ReactElement — это объект, который описывает один React-элемент (обычно создаётся через
createElementили JSX). Это самый базовый тип. Он всегда имеетtype,propsиkey. - JSX.Element — это просто синоним (alias) для
ReactElementс конкретными пропсамиany. По сути, это то же самое, но с более широким типом пропсов. - ReactNode — это супер-тип, который включает всё, что React может отрендерить: ReactElement, строки, числа, булевы значения,
null,undefined, фрагменты, массивы из всего перечисленного.
Когда что использовать?
1. Типизация возвращаемого значения компонента
Если вы пишете функциональный компонент, обычно тип возвращаемого значения можно не указывать — TypeScript сам выведет его из JSX. Но если очень хочется:
// ❌ Неправильно — слишком узко
const BadComponent = (): ReactElement => {
return <div>Hi</div>; // Работает, но не принимает строки
};
// ✅ Правильно — ReactNode (но чаще вообще не нужно)
const GoodComponent = (): ReactNode => {
return <div>Hi</div>; // Работает
};
// Лучший вариант — вообще не указывать тип
const BestComponent = () => {
return <div>Hi</div>; // TypeScript сам выведет JSX.Element
};
2. Типизация children
Это самый частый случай. Вот правильные варианты:
// ✅ Для большинства случаев — ReactNode
interface WrapperProps {
children: ReactNode;
}
// ✅ Если хотите строго один элемент — ReactElement
interface StrictWrapperProps {
children: ReactElement; // Только один элемент, не строка, не массив
}
// ✅ Если children должен быть только React-компонентом — JSX.Element
interface ComponentWrapperProps {
children: JSX.Element; // По сути то же, что ReactElement
}
// Пример использования:
const Card = ({ children }: { children: ReactNode }) => {
return <div className="card">{children}</div>;
};
// Можно передать всё что угодно:
<Card>
Hello World {/* строка — ок */}
{42} {/* число — ок */}
{null} {/* null — ок */}
<span>Hi</span> {/* элемент — ок */}
{[<span>A</span>, <span>B</span>]} {/* массив — ок */}
</Card>
3. Типизация пропсов, которые принимают один элемент
Если ваш компонент принимает иконку или какой-то обязательный элемент:
interface ButtonProps {
icon: ReactElement; // Обязательно один элемент
label: string;
}
const Button = ({ icon, label }: ButtonProps) => {
return (
<button>
{icon}
<span>{label}</span>
</button>
);
};
// Использование:
<Button icon={<StarIcon />} label="Избранное" />
// ❌ Нельзя передать строку: icon="⭐" — ошибка типов
Практические рекомендации
- Для children всегда используйте
ReactNode— это самый гибкий и правильный вариант. - Для возвращаемого типа компонента не указывайте тип вообще — пусть TypeScript выводит сам.
- Для строгих требований (например, иконка должна быть именно React-элементом) используйте
ReactElementилиJSX.Element. - ReactElement и JSX.Element — взаимозаменяемы в 99% случаев, но
ReactElementпредпочтительнее, так как это часть React API.
Наглядная иерархия типов
Если представить типы в виде вложенных кругов:
- Самый внешний круг — ReactNode: строки, числа, null, undefined, boolean, ReactElement, массивы из всего этого.
- Внутри — ReactElement: только объекты, созданные через createElement/JSX.
- Самый внутренний — JSX.Element: практически то же самое, что ReactElement, но с типом пропсов
any.
Надеюсь, теперь стало понятнее! Главное правило: если сомневаетесь — используйте ReactNode для children и не указывайте тип для возврата компонента.
Дополнение: когда ReactNode может быть опасен
Хотя ReactNode — самый универсальный тип, есть ситуации, где его лучше избегать.
Проблема с условным рендерингом
Если вы используете ReactNode для children и внутри компонента применяете React.Children или Children.toArray, TypeScript может не дать вам нормально работать с элементами, потому что ReactNode включает string | number | boolean | null | undefined.
// ❌ Проблема: не можем вызвать методы ReactElement
const BadList = ({ children }: { children: ReactNode }) => {
// Ошибка! children может быть строкой
return <div>{React.Children.map(children, child => child.type)}</div>;
};
// ✅ Решение: используем ReactElement
const GoodList = ({ children }: { children: ReactElement | ReactElement[] }) => {
return <div>{React.Children.map(children, child => child.type)}</div>;
};
Когда использовать JSX.Element вместо ReactElement?
Если вы работаете с библиотекой, которая ожидает именно JSX (например, некоторые CSS-in-JS решения), лучше использовать JSX.Element. Но в обычной разработке разницы нет.
Совет для новичков
Начните с ReactNode для children. Если TypeScript начинает ругаться или вы чувствуете, что нужна более строгая типизация — переходите на ReactElement. И никогда не указывайте тип возврата для компонента — это лишнее.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги