はじめに
Reactでアプリケーションを開発する際、データの流れを理解することは非常に重要です。本記事では、Reactの基本的なデータフローの仕組みと、PropsとStateの使い分けについて解説します。
Reactのデータフローの基本原則
Reactでは、データの流れは親コンポーネントから子コンポーネントへの一方通行が基本です。この設計により、コンポーネントの動作が予測可能になり、アプリケーション全体の構造がシンプルになります。
データフローの特徴
| 特徴 | 説明 |
|---|---|
| 単一方向 | 親から子へのみデータが流れる |
| 予測可能 | データの変更箇所が明確 |
| デバッグ容易 | 問題の原因を特定しやすい |
| 再利用性 | コンポーネントの独立性が高い |
Propsとは
Propsは「Properties」の略で、親コンポーネントから子コンポーネントに渡されるデータです。読み取り専用であり、受け取ったコンポーネント内で変更してはいけません。
Propsの基本的な使い方
// 親コンポーネント
const ParentComponent: React.FC = () => {
const userData = { name: "山田太郎", age: 25 };
return <UserCard user={userData} />;
};
// 子コンポーネント
interface UserCardProps {
user: {
name: string;
age: number;
};
}
const UserCard: React.FC<UserCardProps> = ({ user }) => {
return (
<div>
<h3>{user.name}</h3>
<p>年齢: {user.age}</p>
</div>
);
};
Props検証とデフォルト値
TypeScriptを使用することで、型安全性を確保できます。
interface ButtonProps {
label: string;
onClick?: () => void;
disabled?: boolean;
}
const Button: React.FC<ButtonProps> = ({
label,
onClick,
disabled = false
}) => {
return (
<button onClick={onClick} disabled={disabled}>
{label}
</button>
);
};
Stateとは
Stateはコンポーネント内部で管理される状態です。時間の経過とともに変化する可能性があり、その変更によってコンポーネントの再レンダリングが発生します。
Stateの基本的な使い方
import { useState } from 'react';
const Counter: React.FC = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount(count + 1)}>
増やす
</button>
</div>
);
};
Stateを使った表示制御の例
const Dropdown: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>
メニューを開く
</button>
{isOpen && (
<ul>
<li>オプション1</li>
<li>オプション2</li>
</ul>
)}
</div>
);
};
PropsとStateの使い分け
適切な使い分けがアプリケーションの保守性を大きく左右します。
使い分けの基準
| 項目 | Props | State |
|---|---|---|
| 変更可能性 | 不可(読み取り専用) | 可能 |
| データの所有者 | 親コンポーネント | 自身のコンポーネント |
| 用途 | コンポーネント間のデータ受け渡し | コンポーネント内部の状態管理 |
| 例 | ユーザー情報、設定値 | フォーム入力値、表示/非表示フラグ |
実践的な使用例
interface TodoItemProps {
text: string;
onDelete: (id: string) => void;
id: string;
}
const TodoItem: React.FC<TodoItemProps> = ({ text, onDelete, id }) => {
const [isCompleted, setIsCompleted] = useState(false);
return (
<div>
<input
type="checkbox"
checked={isCompleted}
onChange={() => setIsCompleted(!isCompleted)}
/>
<span style={{
textDecoration: isCompleted ? 'line-through' : 'none'
}}>
{text}
</span>
<button onClick={() => onDelete(id)}>削除</button>
</div>
);
};
ベストプラクティス
1. Stateは最小限に
Stateに格納するデータは必要最小限にしましょう。計算で求められる値はStateに含めません。
// 良い例
const [items, setItems] = useState<string[]>([]);
const itemCount = items.length; // 計算で求める
// 悪い例
const [items, setItems] = useState<string[]>([]);
const [itemCount, setItemCount] = useState(0); // 重複した情報
2. Propsのコピーを避ける
Propsの値をStateにコピーすることは避けましょう。
// 良い例
const UserProfile: React.FC<{ name: string }> = ({ name }) => {
return <h1>{name}</h1>;
};
// 悪い例
const UserProfile: React.FC<{ name: string }> = ({ name }) => {
const [userName, setUserName] = useState(name); // 不要なコピー
return <h1>{userName}</h1>;
};
3. イベントハンドラの適切な受け渡し
const ParentComponent: React.FC = () => {
const handleClick = (id: string) => {
console.log(`Item ${id} clicked`);
};
return <ChildComponent onItemClick={handleClick} />;
};
データフローの図解
親コンポーネント
↓ Props(データ)
↓ Props(イベントハンドラ)
子コンポーネント
↑ イベント発火
内部でStateを管理
まとめ
Reactのデータフローを理解することで、より効率的でメンテナンスしやすいアプリケーションを構築できます。
重要なポイント
- データは親から子へ一方通行で流れます
- Propsは読み取り専用、変更してはいけません
- Stateはコンポーネント内部で管理される変更可能なデータです
- Stateは最小限に、必要なデータのみを保持します
- PropsをStateにコピーしない、単一の情報源を保ちます
これらの原則を守ることで、Reactアプリケーションの品質と保守性が大幅に向上します。最初は複雑に感じるかもしれませんが、実際にコードを書きながら理解を深めていくことが大切です。
次のステップ
- Context APIを使った状態管理
- カスタムフックの作成
- Redux等の状態管理ライブラリの導入
Reactのデータフローをマスターして、より良いアプリケーション開発を目指しましょう。