何が起きているか
変数、プロップ、マップから取り出した値など、何かをJSXコンポーネントとして渡そうとすると、TypeScriptがコンパイルを拒否します。エラーはこのような形で表示されます:
JSX element type 'Component' does not have any construct or call signatures. ts(2604)
これは主に動的レンダリングのシナリオで発生します:コンポーネントを変数に格納する、プロップとして受け取る、ルックアップテーブルから取り出す、といったケースです。値は実行時には問題なく動作します——Reactは何の不満もなく処理します。TypeScriptが型を判断できないため、ビルドをブロックしてしまうのです。
TypeScriptがこのエラーを投げる理由
JSXが有効であるためには、TypeScriptはその要素が関数コンポーネント——(props) => JSX.Element——か、React.Componentを継承したクラスコンポーネントであることを確認しなければなりません。型がany、object、unknown、または関係のないインターフェースである場合、このチェックが失敗します。TypeScriptにはその値がレンダリング可能かどうか判断する方法がないのです。
エラーを再現する
最もシンプルな例:
// ❌ エラーになる
const Component = someMap[key]; // `object` または `any` として推論される
return <Component />; // ts(2604)
少しわかりにくいパターン——プロップ型のアノテーションが間違っている場合:
// ❌ プロップ型が間違っている
interface Props {
icon: object; // TypeScriptからするとただのプレーンオブジェクトであり、コンポーネントではない
}
const Wrapper = ({ icon: Icon }: Props) => {
return <Icon />; // ts(2604)
};
修正1 — 正しいコンポーネントプロップ型を使う
プロップがコンポーネントを想定している場合は、React.ComponentTypeまたはReact.ElementTypeとして型付けします。objectでもanyでもありません。
import React from 'react';
interface Props {
icon: React.ComponentType; // ✅ 関数コンポーネントとクラスコンポーネントの両方をカバー
// プロップを渡す必要がある場合は明示的に:
icon: React.ComponentType<{ size?: number }>;
}
const Wrapper = ({ icon: Icon }: Props) => {
return <Icon />; // ✅
};
"div"や"span"のようなHTMLタグもサポートする必要がありますか?代わりにReact.ElementTypeを使いましょう——コンポーネントとネイティブ要素の両方をカバーします:
interface Props {
as?: React.ElementType; // 'div'、'span'、MyComponent — すべて有効
}
const Box = ({ as: Tag = 'div', children }: React.PropsWithChildren<Props>) => {
return <Tag>{children}</Tag>; // ✅
};
修正2 — コンポーネントマップにアノテーションを付ける
マップからコンポーネントを取り出していますか?マップ自体にアノテーションを付けて、すべての値がコンポーネントであることをTypeScriptに伝えましょう:
import React from 'react';
const componentMap: Record<string, React.ComponentType> = {
home: HomeIcon,
user: UserIcon,
settings: SettingsIcon,
};
const Component = componentMap[key]; // React.ComponentType として型付けされる
return <Component />; // ✅
変更できないサードパーティライブラリから型付けの不十分な値を受け取っている場合は、最終手段として型アサーションが使えます——ただし、その変数の型安全性が失われることを理解した上で使ってください:
const Component = someMap[key] as React.ComponentType;
return <Component />; // ✅ — 使用は最小限に
修正3 — 変数名をパスカルケースに変更する
これは多くの人が意表を突かれるポイントです。JSXは小文字の変数名をコンポーネントではなくHTMLタグとして扱います。型が100%正しくても、小文字の名前は問題を引き起こします:
// ❌ Reactはこれを "component" というHTMLタグとして認識する
const component = MyComponent;
return <component />;
// ✅ パスカルケースはReactにコンポーネントであることを伝える
const Component = MyComponent;
return <Component />;
プロップからの分割代入の場合は、分割代入の時点でそのまま名前を変えましょう:
const { icon: Icon } = props;
return <Icon />; // ✅
修正4 — クラスコンポーネントの宣言を確認する
クラスコンポーネントを書いていますか?必ずReact.Componentを継承する必要があります。継承がなければ、TypeScriptはそれをただのクラスとして扱います——コールシグネチャもコンストラクトシグネチャもなく、ただのオブジェクトです:
// ❌ extends がない — TypeScriptはこれをコンポーネントとして扱わない
class MyButton {
render() {
return <button>クリック</button>;
}
}
// ✅
class MyButton extends React.Component {
render() {
return <button>クリック</button>;
}
}
修正5 — すべてのJSXが壊れている場合はtsconfig.jsonを確認する
ts(2604)が動的なものだけでなくすべてのJSX要素に表示される場合は、型の問題ではなく設定の問題が考えられます。tsconfig.jsonのjsxフィールドを確認してください:
{
"compilerOptions": {
"jsx": "react-jsx", // React 17以降 — 手動インポート不要
// 古い環境の場合は "jsx": "react"
"lib": ["dom", "esnext"]
}
}
修正を確認する
TypeScriptコンパイラを直接実行しましょう——エラーゼロなら問題ありません:
# ファイルを出力せずに型チェックのみ実行
npx tsc --noEmit
# Next.js または Vite プロジェクトの場合
npm run build
VS Codeで修正後にコンポーネント変数にホバーしてみてください。objectやanyではなくReact.ComponentType<...>と表示されるはずです。まだ曖昧な型が表示される場合は、アノテーションが正しく伝播していません。
クイックリファレンス
- プロップとしてのコンポーネント →
React.ComponentTypeまたはReact.ElementTypeとして型付けする - マップからのコンポーネント → マップを
Record<string, React.ComponentType>としてアノテーションする - 小文字の変数名 → JSXで使う前にパスカルケースに変更する
- クラスコンポーネント →
extends React.Componentがあることを確認する - すべてのJSX要素が壊れている →
tsconfig.jsonのjsxフィールドを確認する
学んだこと
ts(2604)は、TypeScriptがコンポーネントでないものを誤ってレンダリングするという実際のバグのカテゴリを検出しています。修正がロジックの問題であることはほぼなく——通常はプロップまたはマップの値に対する、不足しているか間違った型アノテーションの問題です。
記憶しておく価値のある2つのことがあります。1つ目は、コンポーネントのプロップはobjectではなくReact.ComponentTypeとして型付けすること。2つ目は、分割代入したコンポーネントのプロップはJSXで使う前に必ずパスカルケースに変更すること。JSX内の小文字の名前は暗黙的にHTMLタグになります——TypeScriptはフラグを立ててくれますが、そのエラーメッセージから必ずしも原因が明らかになるとは限りません。

