はじめに
Reactを初めて学ぶ人が最初につまづきやすいのが「コンポーネント」の概念です。コンポーネントはUIを部品化し、再利用可能な塊として扱う手法。公式ドキュメント (https://react.dev/learn#components) では「コンポーネントは関数と同じ」と説明していますが、実際にコードを書くときにはPropsやState、Hooksと組み合わせるため、学習のハードルが高く感じられることもあります。本記事ではReact 18+最新情報を交え、関数コンポーネント、クラスコンポーネント、Props、State、Hooks、コンポジションや最適化パターンまで丁寧に解説します。
コンポーネントとは何か
コンポーネントは「UI部品」のようなもので、HTMLとJavaScriptを一体化して定義できます。例えば「ボタン」や「入力フォーム」のようなパーツを1つのファイルとして切り出し、 他の箇所から呼び出して使い回せるのが最大の利点です。
// Button.jsx
export function Button({ label, onClick }) {
return <button onClick={onClick}>{label}</button>;
}
上記のように、Button
コンポーネントを定義し、label
や onClick
をPropsで受け取ることで、使う場所ごとに見た目や挙動を変えられます。
関数コンポーネントの基礎
宣言方法
関数コンポーネントはJavaScriptの関数として定義し、必ず1つのJSX要素を返します。
function Greeting({ name }) {
return <h1>こんにちは、{name}さん!</h1>;
}
ESLintやTypeScript環境では React.FC
型を使って型注釈を付けるケースもあります。
Propsの受け渡し
Propsはコンポーネントを呼び出す側で指定します。
<Greeting name="太郎" />
<Greeting name="花子" />
複数のPropsを使う場合はオブジェクト形式でまとめて受け取り、分割代入で取り出します。
function Profile({ avatarUrl, username, bio }) { /* … */ }
クラスコンポーネントとの違い
かつてReactはクラスコンポーネントが主流でした。2025年現在はHooksの登場で関数コンポーネントが主役ですが、既存コードやライフサイクルメソッドの学習に役立つため、基本を抑えておくと理解が深まります。
import React from 'react';
class Greeting extends React.Component {
render() {
return <h1>こんにちは、{this.props.name}さん!</h1>;
}
}
- State管理:クラスでは
this.state
とthis.setState()
- ライフサイクル:
componentDidMount
,componentDidUpdate
,componentWillUnmount
Hooksを使えば同等の処理が関数コンポーネントで実現可能です。
Stateの扱い方
useStateフック
Stateは「変化する値」を保持し、再レンダーを引き起こす仕組みです。関数コンポーネントでは useState
を使って簡単にStateを定義できます。
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<>
<p>現在のカウント: {count}</p>
<button onClick={() => setCount(count + 1)}>増やす</button>
</>
);
}
- 初期値:
useState(0)
の0
部分 - 更新関数:
setCount
を呼ぶと再レンダーが発生
Stateの注意点
- 不変性:Stateオブジェクトを直接変更せず、必ず更新関数を使う
- 遅延初期化:複雑な計算が必要な場合は関数を渡して一度だけ実行
const [todos, setTodos] = useState(() => loadTodosFromLocalStorage());
Props vs State
項目 | Props | State |
---|---|---|
定義場所 | 親コンポーネントが渡す | 自身のコンポーネント内で定義 |
役割 | 親からの読み取り専用の値 | パーツ内部で変化する値 |
更新 | 親が再レンダーされる必要あり | 更新関数を呼ぶだけで自動的に再レンダー |
Propsを変更したい場合は「親コンポーネント」が再レンダーされて新しいPropsを渡す仕組みになります。
コンポーネントの合成(Composition)
Reactではコンポジションが主流で、親子や兄弟のようにコンポーネントを入れ子にして機能を組み立てます。
function Card({ children }) {
return <div className="card">{children}</div>;
}
function App() {
return (
<Card>
<h2>タイトル</h2>
<p>内容テキスト</p>
</Card>
);
}
- children:
<Card>…</Card>
の中身を受け取る特殊Props - 再利用性:見た目のフレームと中身を分離できるため、多用途に使える
ライフサイクルとuseEffect
useEffectの基本
副作用(データフェッチ、購読、手動DOM操作など)を扱う際に使うフック。
import { useEffect, useState } from 'react';
function DataFetcher({ url }) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then(r => r.json())
.then(json => setData(json));
}, [url]); // urlが変わったときだけ再実行
return data ? <pre>{JSON.stringify(data)}</pre> : <p>読み込み中…</p>;
}
- 第2引数:依存配列。空配列
[]
ならマウント時のみ実行 - クリーンアップ:関数を返すとアンマウント時に実行
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);
クラスのライフサイクル対応
componentDidMount
→useEffect(() => {...}, [])
componentDidUpdate
→useEffect(() => {...}, [deps])
componentWillUnmount
→useEffect(() => { return cleanup }, [])
パフォーマンス最適化
React.memo
関数コンポーネントをラップして、Propsが変わらない限り再レンダーを防ぐ。
const ExpensiveComponent = React.memo(function({ value }) {
// 重い計算...
return <span>{value}</span>;
});
useCallback / useMemo
- useCallback:コールバック関数をメモ化
const handleClick = useCallback(() => { /* ... */ }, [deps]);
- useMemo:計算結果をメモ化
const result = useMemo(() => expensiveCalculation(a, b), [a, b]);
Virtualized List
大量データをリスト表示する場合は react-window
や react-virtualized
を使って、可視領域のみDOM生成する。
コンポーネント設計パターン
Container / Presentational
- Container:データ取得やState管理を担当
- Presentational:UI描画のみを担当
function UserContainer() {
const [user, setUser] = useState(null);
useEffect(() => fetchUser().then(setUser), []);
return user ? <UserProfile data={user} /> : <Loading />;
}
function UserProfile({ data }) { /* UIのみ */ }
Compound Components
親コンポーネントと子要素が連携して振る舞うパターン。
function Tabs({ children }) {
const [active, setActive] = useState(0);
return /* contextでactiveとsetActiveを渡す */;
}
Tabs.List = function({ children }) { /* タブ見出し */ };
Tabs.Panel = function({ index, children }) { /* コンテンツ */ };
テストと型安全
テスト例(React Testing Library)
import { render, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('クリックで増加', () => {
const { getByText } = render(<Counter />);
const btn = getByText(/増やす/);
fireEvent.click(btn);
expect(getByText(/現在のカウント: 1/)).toBeInTheDocument();
});
TypeScriptとの併用
コンポーネントに型注釈を付けると、Propsのうっかりミスをコンパイル時に検出できます。
interface ButtonProps { label: string; onClick: () => void; }
export function Button({ label, onClick }: ButtonProps) { /* ... */ }
まとめ
Reactコンポーネントは「UIを部品化して組み合わせる」ための基本単位です。関数コンポーネントとクラスコンポーネントの違い、PropsとState、Hooks、コンポジション、最適化パターンをしっかり理解すれば、堅牢で再利用性の高いUIを効率的に開発できます。公式ドキュメントを自分のプロジェクトに落とし込む際には、ここで解説した多彩なパターンをぜひ試してみてください。
コメント