JSXとコンポーネント
JSXとコンポーネント
Section titled “JSXとコンポーネント”重要な前提: このドキュメントでは、TSX(TypeScript + JSX)が推奨される理由を明確に説明します。現代のReact開発では、TypeScriptの採用が標準となっており、JSXよりもTSXが選定されることが一般的です。
JSXとTSXの違い
Section titled “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!');JSX vs TSX: どちらを使うべきか
Section titled “JSX vs TSX: どちらを使うべきか”✅ 結論: TSXが推奨されます
現代のReact開発では、TSX(TypeScript + JSX)の使用が推奨されます。以下の理由から、新規プロジェクトではTSXを選択すべきです。
TSXを使うメリット:
- 型安全性: コンパイル時に型エラーを検出し、実行時エラーを防ぐ
- IDEの補完機能: 型情報に基づいた自動補完やリファクタリング支援
- リファクタリングの安全性: 型情報により、安全にコードを変更できる
- ドキュメントとしての機能: 型定義がコードのドキュメントとして機能
- チーム開発での生産性: 型チェックにより、コードレビューが効率的になる
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" /> // コンパイル時にエラー(型が合わない)実務での選択基準:
| 項目 | JSX | TSX |
|---|---|---|
| 型安全性 | ❌ なし | ✅ あり |
| IDE補完 | ⚠️ 限定的 | ✅ 充実 |
| リファクタリング | ⚠️ 手動で確認が必要 | ✅ 型チェックで安全 |
| バグの早期発見 | ⚠️ 実行時まで分からない | ✅ コンパイル時に検出 |
| 学習コスト | ✅ 低い | ⚠️ 中程度 |
| チーム開発 | ⚠️ コードレビューが重要 | ✅ 型チェックで効率的 |
| 推奨度 | ⚠️ 限定的 | ✅ 推奨 |
まとめ:
- ✅ 新規プロジェクト: TSXを使用する
- ✅ 既存のJavaScriptプロジェクト: 段階的にTSXに移行する
- ⚠️ 学習目的: JSXで始めることも可能(ただし、TSXを学ぶことが推奨される)
JSX/TSXの基本構文
Section titled “JSX/TSXの基本構文”JSXとTSXは、基本的な構文は同じです。主な違いは、型定義の有無です。
コンポーネントの種類
Section titled “コンポーネントの種類”関数コンポーネント(推奨)
Section titled “関数コンポーネント(推奨)”現在のReact開発において、関数コンポーネントが標準です。新規プロジェクトでは関数コンポーネントを使用してください。
JSXでの関数コンポーネント:
// JSXfunction 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のメリット:
- ✅ プロパティの型が明確(
nameがstringであることが分かる) - ✅ 誤った型を渡すとコンパイル時にエラーになる
- ✅ IDEの補完が効く
クラスコンポーネント(非推奨)
Section titled “クラスコンポーネント(非推奨)”注意: 現在のReact開発において、新規でクラスコンポーネントを書く機会はほぼありません。関数コンポーネントとHooksで同等の機能を実現できます。
// クラスコンポーネントの例(参考用)class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}!</h1>; }}クラスコンポーネントを使うべき場合:
- 既存のレガシーコードの保守
- 特定のライブラリがクラスコンポーネントを要求する場合(稀)
関数コンポーネントを使うべき場合(推奨):
- ✅ 新規プロジェクト
- ✅ 既存プロジェクトの新規コンポーネント
- ✅ クラスコンポーネントのリファクタリング
Propsは、親コンポーネントから子コンポーネントにデータを渡すための仕組みです。
Propsの基本
Section titled “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の不変性(重要)
Section titled “Propsの不変性(重要)”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のデフォルト値
Section titled “Propsのデフォルト値”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)とデフォルト引数を組み合わせて使用します。
条件レンダリング
Section titled “条件レンダリング”JSXでの条件レンダリング:
// JSXfunction 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では、
0はfalsy値ですが、Reactは0を有効な値として表示します {0 && <Component />}は、0を返すため、画面に0が表示されます{false && <Component />}や{null && <Component />}は何も表示されません
ベストプラクティス:
- 数値の条件レンダリングでは、明示的な比較(
> 0、!== 0など)を使用する - または、三項演算子を使用して
nullを返す
リストレンダリング
Section titled “リストレンダリング”基本的なリストレンダリング
Section titled “基本的なリストレンダリング”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.idやtodo.textの型が明確 - ✅ IDEの補完が効く(
todo.と入力すると、idやtextが表示される)
keyの重要性
Section titled “keyの重要性”keyは、Reactが要素の差分を検知するために必要です。
なぜkeyが必要なのか:
- 差分検知のため: Reactは、前回のレンダリングと現在のレンダリングを比較して、変更された部分だけを更新します
- 効率的な更新: keyがあることで、Reactはどの要素が追加・削除・移動されたかを正確に判断できます
- 状態の保持: リストの順序が変わった際、各要素の状態(フォーカス、入力値など)を正しく保持できます
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> );}なぜ問題なのか:
- リストの順序が変わった場合: 要素が削除・追加・移動されると、インデックスが変わってしまい、Reactが要素を誤って識別します
- 状態の不整合: フォーム入力などの状態が、間違った要素に紐づいてしまいます
- パフォーマンスの低下: 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の選び方:
- 一意性: リスト内で一意であること
- 安定性: レンダリング間で変更されないこと
- 推奨: データベースの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の追加
Section titled “問題: 不要なdivの追加”❌ 問題のあるコード: 不要なdivを追加
// JSXfunction 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での問題:
// TSXtype 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()を使用
Section titled “解決策: Fragment()を使用”✅ 正しいコード: 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> </> );}Fragmentの2つの書き方
Section titled “Fragmentの2つの書き方”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を使うメリット:
- DOMの汚染を防ぐ: 不要な
<div>が追加されない - CSSの簡潔性: 余分な
<div>によるスタイリングの問題を回避 - セマンティックなHTML: 適切なHTML構造を維持
- パフォーマンス: 不要な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を使用することで、コンポーネントの共通化が可能になります。親コンポーネントから子コンポーネントに、任意のコンテンツを渡すことができます。
基本的なchildrenの使い方
Section titled “基本的な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の型定義(TSX)
Section titled “childrenの型定義(TSX)”主要な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要素のみ};
// 特定の型のchildrentype 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>childrenを使った高度なパターン
Section titled “childrenを使った高度なパターン”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の型を明示