Введение: Ваш компонент "молчит", потому что он не умеет рождаться
Вы пишете на TypeScript с React, всё идет как по маслу. И тут — бац! — компилятор выбрасывает в лицо загадочное сообщение:
JSX element type 'MyComponent' does not have any construct or call signatures.
Знакомая картина? Если вы новичок, эта ошибка может показаться заклинанием из параллельного мира. Но не спешите паниковать. На самом деле, всё просто: TypeScript говорит вам, что то, что вы пытаетесь использовать как JSX-компонент, на самом деле не является ни функцией, ни классом, который можно "вызвать" или "сконструировать".
В этой статье мы разберем эту ошибку на пальцах: почему она возникает, как её исправить, и что вообще значат эти "construct or call signatures". Вперёд — разбираться будем весело и с примерами!
Что значит "construct or call signatures"?
Давайте представим, что JSX-компонент — это ресторан. Вы заходите, садитесь за столик (передаете props) и ждете, когда вам принесут еду (return JSX). Но если ресторан закрыт на ремонт или его вообще не существует, вы не сможете сделать заказ.
В TypeScript "construct signature" — это способность класса быть вызванным с new (как конструктор). "Call signature" — это способность функции быть вызванной напрямую. JSX-компоненты в React могут быть либо функциями, либо классами. Если TypeScript не видит у вашего "компонента" ни того, ни другого — он выдаёт ошибку.
Типичный случай:
// Неправильно
const MyComponent = someObject;
// <MyComponent /> — ошибка!
// Правильно
const MyComponent: React.FC<Props> = (props) => {
return <div>Привет</div>;
};
// <MyComponent /> — работает
Ошибка возникает, когда вы пытаетесь использовать в JSX что-то, что не является функцией или классом. Например, строку, число, объект или неинициализированную переменную.
Топ-5 причин ошибки (с примерами кода на TypeScript)
Разберём самые частые сценарии, когда вы можете наступить на эти грабли. Для каждого случая я покажу, как выглядит проблема и как её исправить.
1. Компонент — это объект, а не функция
Самая популярная ловушка. Вы импортируете что-то, думая, что это компонент, а это обычный объект с данными или конфигурацией.
// ❌ Ошибка
import userData from './userData';
// userData = { name: 'Анна', age: 25 }
function App() {
return <userData />; // userData — объект, не компонент!
}
// ✅ Исправление
import UserCard from './UserCard';
function App() {
return <UserCard user={userData} />;
}
Как проверить: наведите курсор на переменную в вашей IDE. Если TypeScript показывает тип object, Record<string, any> или что-то подобное без () => JSX.Element — это не компонент.
2. Компонент — это строка или число
Иногда по невнимательности можно передать в JSX литерал или переменную примитивного типа.
// ❌ Ошибка
const greeting = "Привет, мир!";
function App() {
return <greeting />; // greeting — это string
}
// ✅ Исправление
function App() {
return <h1>{greeting}</h1>; // Используем как текст, а не компонент
}
3. Компонент импортирован неправильно
Если вы используете export default и импортируете без фигурных скобок, но экспорт был именованным — получите undefined. А undefined в JSX — это та самая ошибка.
// В файле Button.tsx
export const Button = () => <button>Click</button>;
// export default отсутствует!
// ❌ Ошибка
import Button from './Button'; // Button = undefined
function App() {
return <Button />;
}
// ✅ Исправление
import { Button } from './Button'; // Именованный импорт
// или добавьте export default в Button.tsx
4. Тип компонента не соответствует JSX
Иногда вы объявляете переменную с типом React.ElementType или ComponentType, но передаёте в неё что-то неподходящее.
// ❌ Ошибка
const MyComponent: React.ElementType = 42;
function App() {
return <MyComponent />;
}
// ✅ Исправление
const MyComponent: React.ElementType = () => <div>Работает!</div>;
5. Компонент — это класс, но без конструктора (старый React)
Если вы используете классовые компоненты, у них должен быть метод render(). Но TypeScript может ругаться, если класс не имплементирует React.Component.
// ❌ Ошибка
class MyComponent {
render() {
return <div>Hello</div>;
}
}
// <MyComponent /> — не работает, потому что класс не extends React.Component
// ✅ Исправление
import React from 'react';
class MyComponent extends React.Component {
render() {
return <div>Hello</div>;
}
}
Как правильно типизировать компоненты в TypeScript?
Чтобы ошибка "JSX element type does not have any construct or call signatures" не заставала вас врасплох, нужно следовать простым правилам типизации. Вот несколько проверенных рецептов.
Для функциональных компонентов
Используйте React.FC (Function Component) или просто объявите тип пропсов вручную.
// Вариант 1: React.FC (с автотипизацией children)
interface CardProps {
title: string;
children?: React.ReactNode;
}
const Card: React.FC<CardProps> = ({ title, children }) => {
return (
<div className="card">
<h2>{title}</h2>
{children}
</div>
);
};
// Вариант 2: Явная типизация возвращаемого значения
const Card = ({ title, children }: CardProps): JSX.Element => {
return (
<div className="card">
<h2>{title}</h2>
{children}
</div>
);
};
Для классовых компонентов
Всегда наследуйтесь от React.Component или React.PureComponent.
interface State {
count: number;
}
class Counter extends React.Component<{}, State> {
state: State = { count: 0 };
render() {
return <button>{this.state.count}</button>;
}
}
Для динамических компонентов (компоненты высшего порядка)
Если вы передаёте компонент как пропс, используйте обобщённые типы.
interface LayoutProps {