Ошибка "JSX element type '...' does not have any construct or call signatures" в TypeScript и React
-
{users.map(user => (
Причина ошибки
Ошибка возникает из-за неправильного использования дженерика React.FC (или React.FunctionComponent). Когда вы пишете React.FC<User>, TypeScript ожидает, что пропсы компонента будут иметь структуру User, но в реальности UserItem принимает пропсы вида { name: string } (из интерфейса User). Однако ключевая проблема в другом: React.FC автоматически добавляет свойство children в тип пропсов, и это может вызывать конфликты.
Но главная причина вашей ошибки — неправильная передача пропсов. Вы используете spread оператор {...user}, который передает все поля объекта user (включая id), но ваш компонент UserItem ожидает только name. TypeScript видит, что тип пропсов не совпадает, и не может определить, как вызывать компонент.
Решение проблемы
Есть несколько способов исправить эту ошибку:
Способ 1: Явно указать ожидаемые пропсы
// UserList.tsx
import React from 'react';
interface User {
id: number;
name: string;
}
// Правильное определение — без React.FC
const UserItem = ({ name }: { name: string }) => {
return <li>{name}</li>;
};
// Или с React.FC, но с правильным типом пропсов
// const UserItem: React.FC<{ name: string }> = ({ name }) => { ... };
function UserList({ users }: { users: User[] }) {
return (
<ul>
{users.map(user => (
<UserItem key={user.id} name={user.name} /> // ← передаем только name
))}
</ul>
);
}
Способ 2: Использовать интерфейс для пропсов
interface UserItemProps {
name: string;
}
const UserItem: React.FC<UserItemProps> = ({ name }) => {
return <li>{name}</li>;
};
Способ 3: Избегать React.FC (рекомендуется в новых проектах)
Многие разработчики отказываются от React.FC, потому что он:
- Автоматически добавляет
children(даже если они не нужны) - Не поддерживает дженерики (generic components)
- Усложняет работу с
defaultProps
Вместо этого используйте обычную функцию с явным типом пропсов:
const UserItem = ({ name }: { name: string }): JSX.Element => {
return <li>{name}</li>;
};
Почему это работает?
Когда вы определяете компонент как const UserItem = (props: SomeType) => ..., TypeScript видит, что это функция, принимающая один аргумент (пропсы) и возвращающая JSX. Это удовлетворяет требованиям JSX — компонент становится "вызываемым" с помощью синтаксиса <UserItem />.
Если же вы используете React.FC<User>, где User — это интерфейс с полями id и name, а передаете только name, TypeScript не может сопоставить типы и выдает ошибку.
Дополнительный совет
Если вам нужно передать все поля объекта, но не все они нужны компоненту, используйте Pick или Omit:
// Берем только нужные поля
const UserItem = ({ name }: Pick<User, 'name'>) => {
return <li>{name}</li>;
};
Или явно деструктуризируйте пропсы при передаче:
<UserItem key={user.id} name={user.name} />
<<Альтернативный подход: использование дженериков
Если вы часто работаете с компонентами, которые принимают часть данных из объекта, можно создать универсальный компонент с помощью дженериков:
interface User {
id: number;
name: string;
email: string;
}
// Дженерик-компонент, который принимает часть полей
function UserItem<T extends Record<string, any>>({ data, fields }: { data: T; fields: (keyof T)[] }) {
return (
<li>
{fields.map(field => (
<span key={String(field)}>{data[field]}</span>
))}
</li>
);
}
// Использование
function UserList({ users }: { users: User[] }) {
return (
<ul>
{users.map(user => (
<UserItem key={user.id} data={user} fields={['name']} />
))}
</ul>
);
}
Этот подход особенно полезен, когда у вас есть много разных компонентов, которые отображают разные наборы полей из одного и того же объекта. Однако помните, что для простых случаев лучше явно указывать пропсы — это делает код более читаемым и предсказуемым.
Еще один вариант — использовать render props или children as function, но это уже выходит за рамки простого решения вашей проблемы.
Ответов пока нет.
Войдите чтобы оставить ответ