Skip to content

JSXとコンポーネント

重要な前提: このドキュメントでは、TSX(TypeScript + JSX)が推奨される理由を明確に説明します。現代のReact開発では、TypeScriptの採用が標準となっており、JSXよりもTSXが選定されることが一般的です。

**JSX(JavaScript XML)**は、JavaScriptの拡張構文で、HTMLライクな記述でReact要素を作成できます。JavaScriptファイル(.js)またはJavaScript拡張ファイル(.jsx)で使用されます。

// JSX (JavaScript)
const element = <h1>Hello, World!</h1>;
// コンパイル後(React.createElement)
const element = React.createElement('h1', null, 'Hello, World!');

**TSX(TypeScript XML)**は、TypeScriptの拡張構文で、JSXと同様の記述が可能ですが、型安全性が追加されています。TypeScriptファイル(.tsx)で使用されます。

// TSX (TypeScript)
const element: JSX.Element = <h1>Hello, World!</h1>;
// コンパイル後(React.createElement、型情報も保持)
const element = React.createElement('h1', null, 'Hello, World!');

✅ 結論: TSXが推奨されます

現代のReact開発では、TSX(TypeScript + JSX)の使用が推奨されます。以下の理由から、新規プロジェクトではTSXを選択すべきです。

TSXを使うメリット:

  1. 型安全性: コンパイル時に型エラーを検出し、実行時エラーを防ぐ
  2. IDEの補完機能: 型情報に基づいた自動補完やリファクタリング支援
  3. リファクタリングの安全性: 型情報により、安全にコードを変更できる
  4. ドキュメントとしての機能: 型定義がコードのドキュメントとして機能
  5. チーム開発での生産性: 型チェックにより、コードレビューが効率的になる

JSXを使う場合(限定的):

  • 既存のJavaScriptプロジェクトへの段階的な移行
  • 非常にシンプルなプロジェクト(学習目的など)
  • TypeScriptの導入コストが大きい場合(ただし、長期的にはTSXが推奨)

比較例: 同じコンポーネントをJSXとTSXで書いた場合

// ❌ JSX: 型チェックがない
function UserCard({ name, email, age }) {
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
<p>Age: {age}</p>
</div>
);
}
// 問題点:
// 1. プロパティの型が不明(name, email, ageが何の型か分からない)
// 2. 誤った型を渡してもエラーにならない(実行時エラーの可能性)
// 3. IDEの補完が効かない
<UserCard name={123} email={456} age="thirty" /> // エラーにならない(実行時に問題が発生)
// ✅ TSX: 型安全性がある
type UserCardProps = {
name: string;
email: string;
age: number;
};
function UserCard({ name, email, age }: UserCardProps) {
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
<p>Age: {age}</p>
</div>
);
}
// メリット:
// 1. プロパティの型が明確(name, email, ageの型が定義されている)
// 2. 誤った型を渡すとコンパイル時にエラーになる
// 3. IDEの補完が効く(name, email, ageの入力候補が表示される)
<UserCard name={123} email={456} age="thirty" /> // コンパイル時にエラー(型が合わない)

実務での選択基準:

項目JSXTSX
型安全性❌ なし✅ あり
IDE補完⚠️ 限定的✅ 充実
リファクタリング⚠️ 手動で確認が必要✅ 型チェックで安全
バグの早期発見⚠️ 実行時まで分からない✅ コンパイル時に検出
学習コスト✅ 低い⚠️ 中程度
チーム開発⚠️ コードレビューが重要✅ 型チェックで効率的
推奨度⚠️ 限定的推奨

まとめ:

  • 新規プロジェクト: TSXを使用する
  • 既存のJavaScriptプロジェクト: 段階的にTSXに移行する
  • ⚠️ 学習目的: JSXで始めることも可能(ただし、TSXを学ぶことが推奨される)

JSXとTSXは、基本的な構文は同じです。主な違いは、型定義の有無です。

現在のReact開発において、関数コンポーネントが標準です。新規プロジェクトでは関数コンポーネントを使用してください。

JSXでの関数コンポーネント:

// JSX
function Welcome({ name }) {
return <h1>Hello, {name}!</h1>;
}
// アロー関数
const Welcome = ({ name }) => {
return <h1>Hello, {name}!</h1>;
};

TSXでの関数コンポーネント(推奨):

// TSX: 型定義を明示
type WelcomeProps = {
name: string;
};
function Welcome({ name }: WelcomeProps) {
return <h1>Hello, {name}!</h1>;
}
// アロー関数
const Welcome = ({ name }: WelcomeProps) => {
return <h1>Hello, {name}!</h1>;
};
// インライン型定義(簡潔)
function Welcome({ name }: { name: string }) {
return <h1>Hello, {name}!</h1>;
}

TSXのメリット:

  • ✅ プロパティの型が明確(namestringであることが分かる)
  • ✅ 誤った型を渡すとコンパイル時にエラーになる
  • ✅ IDEの補完が効く

クラスコンポーネント(非推奨)

Section titled “クラスコンポーネント(非推奨)”

注意: 現在のReact開発において、新規でクラスコンポーネントを書く機会はほぼありません。関数コンポーネントとHooksで同等の機能を実現できます。

// クラスコンポーネントの例(参考用)
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}

クラスコンポーネントを使うべき場合:

  • 既存のレガシーコードの保守
  • 特定のライブラリがクラスコンポーネントを要求する場合(稀)

関数コンポーネントを使うべき場合(推奨):

  • ✅ 新規プロジェクト
  • ✅ 既存プロジェクトの新規コンポーネント
  • ✅ クラスコンポーネントのリファクタリング

Propsは、親コンポーネントから子コンポーネントにデータを渡すための仕組みです。

JSXでのProps:

// JSX: 型定義なし
function UserCard({ name, email, age }) {
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
<p>Age: {age}</p>
</div>
);
}
// 使用例
<UserCard name="John Doe" email="john@example.com" age={30} />

TSXでのProps(推奨):

// TSX: 型定義を明示
type UserCardProps = {
name: string;
email: string;
age: number;
};
function UserCard({ name, email, age }: UserCardProps) {
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
<p>Age: {age}</p>
</div>
);
}
// 使用例
<UserCard name="John Doe" email="john@example.com" age={30} />
<UserCard name={123} email={456} age="thirty" /> // ❌ コンパイル時にエラー

TSXのメリット:

  • ✅ プロパティの型が明確
  • ✅ 誤った型を渡すとコンパイル時にエラーになる
  • ✅ IDEの補完が効く(name, email, ageの入力候補が表示される)

Propsは読み取り専用(readOnly)です。 Propsを直接変更することはできません。

// ❌ 悪い例: propsを直接変更しようとする
function UserCard({ user }) {
user.name = 'New Name'; // ❌ エラー: propsは読み取り専用
return <div>{user.name}</div>;
}
// ✅ 正しい例: propsを読み取るだけ
function UserCard({ user }) {
return <div>{user.name}</div>;
}
// ✅ 正しい例: 新しいオブジェクトを作成する
function UserCard({ user }) {
const updatedUser = {
...user,
name: 'New Name'
};
return <div>{updatedUser.name}</div>;
}

重要なポイント:

  • Propsは不変: React Compiler使用時、propsは不変として扱われます
  • 状態の変更はuseStateを使用: コンポーネント内で状態を変更したい場合は、useStateを使用します
  • イミュータブルな操作: オブジェクトや配列を変更する場合は、新しいオブジェクト/配列を作成します

Propsにデフォルト値を設定できます。

JSXでのデフォルト値:

// JSX: デフォルト引数
function UserCard({ name = 'Anonymous', email = 'no-email', age = 0 }) {
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
<p>Age: {age}</p>
</div>
);
}
// デフォルト値の設定方法2: defaultProps(非推奨)
function UserCard({ name, email, age }) {
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
<p>Age: {age}</p>
</div>
);
}
UserCard.defaultProps = {
name: 'Anonymous',
email: 'no-email',
age: 0
};

TSXでのデフォルト値(推奨):

// TSX: デフォルト引数(推奨)
type UserCardProps = {
name?: string; // オプショナルプロパティ
email?: string;
age?: number;
};
function UserCard({
name = 'Anonymous',
email = 'no-email',
age = 0
}: UserCardProps) {
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
<p>Age: {age}</p>
</div>
);
}
// 使用例(デフォルト値が適用される)
<UserCard /> // name='Anonymous', email='no-email', age=0
<UserCard name="John" /> // name='John', email='no-email', age=0

注意: defaultPropsは非推奨です。デフォルト引数を使用することを推奨します。TSXでは、オプショナルプロパティ(name?: string)とデフォルト引数を組み合わせて使用します。

JSXでの条件レンダリング:

// JSX
function Greeting({ isLoggedIn }) {
if (isLoggedIn) {
return <h1>Welcome back!</h1>;
}
return <h1>Please sign in.</h1>;
}
// 三項演算子
function Greeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? <h1>Welcome back!</h1> : <h1>Please sign in.</h1>}
</div>
);
}

TSXでの条件レンダリング(推奨):

// TSX: 型定義を明示
type GreetingProps = {
isLoggedIn: boolean;
};
function Greeting({ isLoggedIn }: GreetingProps) {
if (isLoggedIn) {
return <h1>Welcome back!</h1>;
}
return <h1>Please sign in.</h1>;
}
// 三項演算子
function Greeting({ isLoggedIn }: GreetingProps) {
return (
<div>
{isLoggedIn ? <h1>Welcome back!</h1> : <h1>Please sign in.</h1>}
</div>
);
}
// 論理AND演算子
type MailboxProps = {
unreadMessages: string[];
};
function Mailbox({ unreadMessages }: MailboxProps) {
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 && (
<h2>You have {unreadMessages.length} unread messages.</h2>
)}
</div>
);
}

条件レンダリングの「数値の0」トラップ(重要)

Section titled “条件レンダリングの「数値の0」トラップ(重要)”

論理AND演算子(&&)を使用する際、数値の0が表示されてしまうというバグがよく発生します。TSXでは型定義により、この問題をより明確に認識できます。

JSXでの問題:

// ❌ 問題のあるコード(JSX)
function Counter({ count }) {
return (
<div>
<h1>Count</h1>
{count && <p>Count is {count}</p>}
{/* countが0の場合、画面に「0」と表示されてしまう */}
</div>
);
}

TSXでの正しいコード(推奨):

// ✅ 正しいコード: 型定義を明示(TSX)
type CounterProps = {
count: number;
};
function Counter({ count }: CounterProps) {
return (
<div>
<h1>Count</h1>
{count > 0 && <p>Count is {count}</p>}
{/* countが0の場合は何も表示されない */}
</div>
);
}
// ✅ 正しいコード: 三項演算子を使用
function Counter({ count }: CounterProps) {
return (
<div>
<h1>Count</h1>
{count > 0 ? <p>Count is {count}</p> : null}
</div>
);
}
// ✅ 正しいコード: ブール値に変換
function Counter({ count }: CounterProps) {
return (
<div>
<h1>Count</h1>
{Boolean(count) && <p>Count is {count}</p>}
</div>
);
}

TSXのメリット:

  • ✅ 型定義により、countが数値であることが明確
  • ✅ IDEの補完により、適切な比較演算子(> 0など)を提案
  • ✅ 型チェックにより、誤った型変換を防止

なぜこの問題が発生するのか:

  • JavaScriptでは、0falsy値ですが、Reactは0を有効な値として表示します
  • {0 && <Component />}は、0を返すため、画面に0が表示されます
  • {false && <Component />}{null && <Component />}は何も表示されません

ベストプラクティス:

  • 数値の条件レンダリングでは、明示的な比較(> 0!== 0など)を使用する
  • または、三項演算子を使用してnullを返す

JSXでのリストレンダリング:

// JSX: 型定義なし
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}

TSXでのリストレンダリング(推奨):

// TSX: 型定義を明示
type Todo = {
id: number;
text: string;
};
type TodoListProps = {
todos: Todo[];
};
function TodoList({ todos }: TodoListProps) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}

TSXのメリット:

  • ✅ 配列の要素の型が明確(Todo[]であることが分かる)
  • todo.idtodo.textの型が明確
  • ✅ IDEの補完が効く(todo.と入力すると、idtextが表示される)

keyは、Reactが要素の差分を検知するために必要です。

なぜkeyが必要なのか:

  1. 差分検知のため: Reactは、前回のレンダリングと現在のレンダリングを比較して、変更された部分だけを更新します
  2. 効率的な更新: keyがあることで、Reactはどの要素が追加・削除・移動されたかを正確に判断できます
  3. 状態の保持: リストの順序が変わった際、各要素の状態(フォーカス、入力値など)を正しく保持できます

keyがない場合の問題:

// ❌ 問題のあるコード: keyがない
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li>{todo.text}</li> {/* ⚠️ 警告: keyが必要 */}
))}
</ul>
);
}

問題点:

  • Reactが要素の変更を正確に検知できない
  • パフォーマンスの低下
  • 予期しない再レンダリング
  • 状態の不整合(入力フィールドの値がずれるなど)

インデックス(index)をkeyにしてはいけない理由

Section titled “インデックス(index)をkeyにしてはいけない理由”

❌ 悪い例: インデックスをkeyに使用

function TodoList({ todos }) {
return (
<ul>
{todos.map((todo, index) => (
<li key={index}>{todo.text}</li> {/* ❌ 問題: インデックスをkeyに使用 */}
))}
</ul>
);
}

なぜ問題なのか:

  1. リストの順序が変わった場合: 要素が削除・追加・移動されると、インデックスが変わってしまい、Reactが要素を誤って識別します
  2. 状態の不整合: フォーム入力などの状態が、間違った要素に紐づいてしまいます
  3. パフォーマンスの低下: Reactが不要な再レンダリングを行ってしまいます

実際のバグ例:

// 問題のあるコード
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo, index) => (
<li key={index}>
<input defaultValue={todo.text} />
{/* 問題: 最初の要素を削除すると、2番目の要素の入力値が1番目に移動してしまう */}
</li>
))}
</ul>
);
}
// 初期状態: ['Todo 1', 'Todo 2', 'Todo 3']
// ユーザーが「Todo 2」の入力欄に「Updated」と入力
// 「Todo 1」を削除
// 結果: 「Todo 2」の入力値「Updated」が「Todo 1」の位置に移動してしまう(バグ!)

✅ 正しい例: 一意のIDをkeyに使用

function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li> {/* ✅ 正しい: 一意のIDをkeyに使用 */}
))}
</ul>
);
}

keyの選び方:

  1. 一意性: リスト内で一意であること
  2. 安定性: レンダリング間で変更されないこと
  3. 推奨: データベースのID、UUID、または一意の識別子を使用
// ✅ 良い例: データベースのIDを使用
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
// ✅ 良い例: UUIDを使用
{todos.map(todo => (
<li key={todo.uuid}>{todo.text}</li>
))}
// ⚠️ 避けるべき: インデックスを使用(順序が変わらない場合のみ許容)
{todos.map((todo, index) => (
<li key={index}>{todo.text}</li> {/* 順序が変わらない場合のみ */}
))}
// ❌ 避けるべき: ランダムな値を生成
{todos.map(todo => (
<li key={Math.random()}>{todo.text}</li> {/* 毎回新しいkeyが生成される */}
))}

まとめ:

  • 一意のIDを使用: key={item.id}
  • 安定した値を使用: レンダリング間で変更されない値
  • インデックスを避ける: リストの順序が変わる可能性がある場合
  • ランダム値を避ける: 毎回新しいkeyが生成されるため、パフォーマンスが低下

Fragment(): 無駄なdivを増やさないための必須知識

Section titled “Fragment(): 無駄なdivを増やさないための必須知識”

Reactでは、コンポーネントは1つの要素を返す必要があります。複数の要素を返す場合、通常は<div>で囲む必要がありますが、Fragment(<></>)を使用することで、不要な<div>を追加せずに済みます

❌ 問題のあるコード: 不要なdivを追加

// JSX
function UserList({ users }) {
return (
<div> {/* 不要な<div> */}
<h1>Users</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}

問題点:

  • 不要な<div>がDOMに追加される
  • CSSのスタイリングが複雑になる(余分な<div>の影響)
  • セマンティックなHTML構造が崩れる

TSXでの問題:

// TSX
type User = {
id: number;
name: string;
};
type UserListProps = {
users: User[];
};
function UserList({ users }: UserListProps) {
return (
<div> {/* 不要な<div> */}
<h1>Users</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}

✅ 正しいコード: Fragmentを使用(推奨)

// JSX: Fragment(短縮記法)
function UserList({ users }) {
return (
<>
<h1>Users</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
}

TSXでのFragment(推奨):

// TSX: Fragment(短縮記法)
type User = {
id: number;
name: string;
};
type UserListProps = {
users: User[];
};
function UserList({ users }: UserListProps) {
return (
<>
<h1>Users</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
}

1. 短縮記法(<></>): 推奨

function Component() {
return (
<>
<h1>Title</h1>
<p>Content</p>
</>
);
}

2. 明示的な記法(<React.Fragment>): keyが必要な場合のみ

import { Fragment } from 'react';
function Component() {
return (
<Fragment>
<h1>Title</h1>
<p>Content</p>
</Fragment>
);
}
// keyが必要な場合(リストレンダリング)
function List({ items }: { items: string[] }) {
return (
<>
{items.map(item => (
<Fragment key={item.id}>
<h2>{item.title}</h2>
<p>{item.content}</p>
</Fragment>
))}
</>
);
}

Fragmentを使うメリット:

  1. DOMの汚染を防ぐ: 不要な<div>が追加されない
  2. CSSの簡潔性: 余分な<div>によるスタイリングの問題を回避
  3. セマンティックなHTML: 適切なHTML構造を維持
  4. パフォーマンス: 不要なDOMノードが減る(微々たる差だが、積み重ねが重要)

実践例: 条件付きレンダリングとFragment

type ConditionalListProps = {
items: string[];
showTitle: boolean;
};
function ConditionalList({ items, showTitle }: ConditionalListProps) {
return (
<>
{showTitle && <h1>Items</h1>}
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</>
);
}

まとめ:

  • Fragmentを使用: 不要な<div>を追加しない
  • 短縮記法(<></>)を推奨: シンプルで読みやすい
  • keyが必要な場合のみ明示的な記法を使用: <React.Fragment key={...}>
  • 不要な<div>を避ける: DOMの汚染とCSSの問題を防ぐ

Event Handling: onClick=() => handleClick(id) などの書き方

Section titled “Event Handling: onClick= などの書き方”

Reactでは、イベントハンドラーをpropsとして渡すことができます。関数を直接渡すことで、動的なイベント処理が可能になります。

基本的なイベントハンドリング

Section titled “基本的なイベントハンドリング”

JSXでのイベントハンドリング:

// JSX: 基本的なイベントハンドリング
function Button() {
const handleClick = () => {
console.log('Button clicked!');
};
return <button onClick={handleClick}>Click me</button>;
}
// インライン関数
function Button() {
return <button onClick={() => console.log('Button clicked!')}>Click me</button>;
}

TSXでのイベントハンドリング(推奨):

// TSX: 型定義を明示
function Button() {
const handleClick = (): void => {
console.log('Button clicked!');
};
return <button onClick={handleClick}>Click me</button>;
}
// インライン関数
function Button() {
return <button onClick={() => console.log('Button clicked!')}>Click me</button>;
}

パラメータを渡すイベントハンドリング

Section titled “パラメータを渡すイベントハンドリング”

重要なポイント: イベントハンドラーにパラメータを渡す場合、アロー関数を使用します。

JSXでのパラメータ付きイベントハンドリング:

// JSX: パラメータを渡す
function TodoList({ todos }) {
const handleClick = (id) => {
console.log('Todo clicked:', id);
};
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<button onClick={() => handleClick(todo.id)}>
{todo.text}
</button>
</li>
))}
</ul>
);
}

TSXでのパラメータ付きイベントハンドリング(推奨):

// TSX: 型定義を明示
type Todo = {
id: number;
text: string;
};
type TodoListProps = {
todos: Todo[];
};
function TodoList({ todos }: TodoListProps) {
const handleClick = (id: number): void => {
console.log('Todo clicked:', id);
};
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<button onClick={() => handleClick(todo.id)}>
{todo.text}
</button>
</li>
))}
</ul>
);
}

イベントオブジェクトを使用する場合

Section titled “イベントオブジェクトを使用する場合”

TSXでのイベントオブジェクトの型定義:

// TSX: イベントオブジェクトの型定義
function Input() {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
console.log('Input value:', e.target.value);
};
return <input type="text" onChange={handleChange} />;
}
// パラメータとイベントオブジェクトの両方を使用
type TodoItemProps = {
id: number;
text: string;
onToggle: (id: number) => void;
};
function TodoItem({ id, text, onToggle }: TodoItemProps) {
const handleClick = (e: React.MouseEvent<HTMLButtonElement>): void => {
e.preventDefault(); // イベントのデフォルト動作を防ぐ
onToggle(id);
};
return (
<button onClick={handleClick}>
{text}
</button>
);
}

よくあるイベントハンドリングのパターン

Section titled “よくあるイベントハンドリングのパターン”

1. リストアイテムのクリック処理

type Product = {
id: number;
name: string;
price: number;
};
type ProductListProps = {
products: Product[];
onProductClick: (product: Product) => void;
};
function ProductList({ products, onProductClick }: ProductListProps) {
return (
<ul>
{products.map(product => (
<li key={product.id}>
<button onClick={() => onProductClick(product)}>
{product.name} - {product.price}
</button>
</li>
))}
</ul>
);
}

2. フォームの送信処理

type FormProps = {
onSubmit: (data: { name: string; email: string }) => void;
};
function Form({ onSubmit }: FormProps) {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
e.preventDefault(); // フォームのデフォルト送信を防ぐ
const formData = new FormData(e.currentTarget);
const name = formData.get('name') as string;
const email = formData.get('email') as string;
onSubmit({ name, email });
};
return (
<form onSubmit={handleSubmit}>
<input type="text" name="name" />
<input type="email" name="email" />
<button type="submit">Submit</button>
</form>
);
}

3. 条件付きイベントハンドリング

type ButtonProps = {
disabled?: boolean;
onClick: () => void;
};
function Button({ disabled = false, onClick }: ButtonProps) {
const handleClick = (): void => {
if (!disabled) {
onClick();
}
};
return (
<button onClick={handleClick} disabled={disabled}>
Click me
</button>
);
}

イベントハンドラーの型定義(TSX)

Section titled “イベントハンドラーの型定義(TSX)”

主要なイベント型:

// マウスイベント
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {}}
onMouseEnter={(e: React.MouseEvent<HTMLDivElement>) => {}}
// フォームイベント
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {}}
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {}}
// キーボードイベント
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {}}
onKeyPress={(e: React.KeyboardEvent<HTMLInputElement>) => {}}
// フォーカスイベント
onFocus={(e: React.FocusEvent<HTMLInputElement>) => {}}
onBlur={(e: React.FocusEvent<HTMLInputElement>) => {}}

まとめ:

  • アロー関数でパラメータを渡す: onClick={() => handleClick(id)}
  • イベントオブジェクトの型を明示: TSXではReact.MouseEvent<HTMLButtonElement>など
  • preventDefault()を使用: フォーム送信などのデフォルト動作を防ぐ
  • 型安全性を確保: TSXでイベントハンドラーの型を明示

Children: props.children を使ったコンポーネントの共通化

Section titled “Children: props.children を使ったコンポーネントの共通化”

props.childrenを使用することで、コンポーネントの共通化が可能になります。親コンポーネントから子コンポーネントに、任意のコンテンツを渡すことができます。

JSXでのchildren:

// JSX: childrenを使用
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
// 使用例
<Card>
<h2>Title</h2>
<p>Content</p>
</Card>

TSXでのchildren(推奨):

// TSX: childrenの型定義
import { ReactNode } from 'react';
type CardProps = {
children: ReactNode;
};
function Card({ children }: CardProps) {
return (
<div className="card">
{children}
</div>
);
}
// 使用例
<Card>
<h2>Title</h2>
<p>Content</p>
</Card>

主要なchildrenの型:

import { ReactNode, ReactElement, JSX.Element } from 'react';
// ReactNode: 最も一般的(推奨)
type Props = {
children: ReactNode; // 文字列、数値、要素、配列など、すべてのReact要素を受け入れる
};
// ReactElement: 単一のReact要素のみ
type Props = {
children: ReactElement; // 単一のReact要素のみ
};
// JSX.Element: JSX要素のみ
type Props = {
children: JSX.Element; // JSX要素のみ
};
// 特定の型のchildren
type Props = {
children: string; // 文字列のみ
};
type Props = {
children: ReactElement | ReactElement[]; // 単一または複数の要素
};

実践例: レイアウトコンポーネント

Section titled “実践例: レイアウトコンポーネント”

1. カードコンポーネント

import { ReactNode } from 'react';
type CardProps = {
title?: string;
children: ReactNode;
};
function Card({ title, children }: CardProps) {
return (
<div className="card">
{title && <h2 className="card-title">{title}</h2>}
<div className="card-content">
{children}
</div>
</div>
);
}
// 使用例
<Card title="User Profile">
<p>Name: John Doe</p>
<p>Email: john@example.com</p>
</Card>

2. モーダルコンポーネント

import { ReactNode } from 'react';
type ModalProps = {
isOpen: boolean;
onClose: () => void;
children: ReactNode;
};
function Modal({ isOpen, onClose, children }: ModalProps) {
if (!isOpen) return null;
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<button className="modal-close" onClick={onClose}>×</button>
{children}
</div>
</div>
);
}
// 使用例
<Modal isOpen={isOpen} onClose={() => setIsOpen(false)}>
<h2>Modal Title</h2>
<p>Modal content goes here</p>
</Modal>

3. レイアウトコンポーネント

import { ReactNode } from 'react';
type LayoutProps = {
header?: ReactNode;
sidebar?: ReactNode;
children: ReactNode;
footer?: ReactNode;
};
function Layout({ header, sidebar, children, footer }: LayoutProps) {
return (
<div className="layout">
{header && <header className="layout-header">{header}</header>}
<div className="layout-body">
{sidebar && <aside className="layout-sidebar">{sidebar}</aside>}
<main className="layout-main">{children}</main>
</div>
{footer && <footer className="layout-footer">{footer}</footer>}
</div>
);
}
// 使用例
<Layout
header={<h1>My App</h1>}
sidebar={<nav>Navigation</nav>}
footer={<p>© 2024 My App</p>}
>
<h2>Main Content</h2>
<p>This is the main content area.</p>
</Layout>

4. 条件付きレンダリングとchildren

import { ReactNode } from 'react';
type ConditionalWrapperProps = {
condition: boolean;
wrapper: (children: ReactNode) => ReactNode;
children: ReactNode;
};
function ConditionalWrapper({ condition, wrapper, children }: ConditionalWrapperProps) {
return condition ? <>{wrapper(children)}</> : <>{children}</>;
}
// 使用例
<ConditionalWrapper
condition={isLoading}
wrapper={(children) => <div className="loading">{children}</div>}
>
<p>Content</p>
</ConditionalWrapper>

1. 複数のchildren(名前付きchildren)

import { ReactNode } from 'react';
type CardProps = {
header?: ReactNode;
body: ReactNode;
footer?: ReactNode;
};
function Card({ header, body, footer }: CardProps) {
return (
<div className="card">
{header && <div className="card-header">{header}</div>}
<div className="card-body">{body}</div>
{footer && <div className="card-footer">{footer}</div>}
</div>
);
}
// 使用例
<Card
header={<h2>Title</h2>}
body={<p>Content</p>}
footer={<button>Action</button>}
/>

2. render propsパターン

import { ReactNode } from 'react';
type DataProviderProps<T> = {
data: T;
children: (data: T) => ReactNode;
};
function DataProvider<T>({ data, children }: DataProviderProps<T>) {
return <>{children(data)}</>;
}
// 使用例
<DataProvider data={user}>
{(user) => (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
)}
</DataProvider>

まとめ:

  • ReactNodeを使用: 最も柔軟な型定義(推奨)
  • コンポーネントの共通化: childrenで任意のコンテンツを受け入れる
  • レイアウトコンポーネント: childrenで柔軟なレイアウトを実現
  • 型安全性を確保: TSXでchildrenの型を明示