React(リアクト)は現在、世界中で最も人気のあるフロントエンドライブラリの一つです。そして、モダンなReact開発において中心的な役割を果たしているのが**「Hooks(フックス)」**と呼ばれる機能群です。
かつてのReactでは、状態(State)やライフサイクル(コンポーネントの生成から破棄までのタイミング)を管理するためには「クラスコンポーネント」を書く必要があり、コードが複雑で巨大になりがちでした。しかし、バージョン16.8でHooksが登場したことで、すべてをシンプルで完結な「関数コンポーネント」の中で完結させることができるようになりました。
本記事では、React Hooksの基礎である useState と useEffect に焦点を当てて、その使い方から注意点、実践的なテクニックまでを網羅的に解説します。この記事さえ読めば、Reactコンポーネントの基礎的な振る舞いはすべて理解できるはずです。
1. 状態管理の基礎:useState とは?
useState は、コンポーネント内でUIの「状態(State)」を保持し、状態が変更されたときにコンポーネントを再レンダリング(画面の再描画)させるためのフックです。
ユーザーの入力内容、ボタンがクリックされた回数、モーダルウィンドウの開閉状態など、アプリ内で変化するあらゆるデータは useState を使って管理します。
useState の基本構文
useState は以下のように、配列の分割代入を用いて宣言するのが一般的です。
import { useState } from 'react';
function Counter() {
// state変数(count)と、それを更新する関数(setCount)を宣言
// useStateの引数(0)は、そのstateの初期値です
const [count, setCount] = useState(0);
return (
<div>
<p>現在のカウント: {count}</p>
{/* ボタンがクリックされたら setCount を呼び出して状態を更新 */}
<button onClick={() => setCount(count + 1)}>
プラス1
</button>
<button onClick={() => setCount(count - 1)}>
マイナス1
</button>
</div>
);
}
useState の動きの仕組み
- 初期レンダリング時: コンポーネントが描画される際、
countは初期値である0を保持します。 - ボタンクリック時:
onClickにバインドされた関数が走り、setCount(count + 1)が実行されます。これによりReactは「状態が更新された」と認識します。 - 再レンダリング: 状態の変更を検知したReactは、
Counterコンポーネントを再実行(再レンダリング)し、最新のcount(例: 1)を持った新しいUIを画面に反映させます。
⚠️ よくある罠:オブジェクトや配列の更新
useState でオブジェクトや配列を扱う場合は注意が必要です。状態を更新する時は、元のオブジェクトを直接書き換える(ミューテートする)のではなく、必ず新しいオブジェクト(または配列)を作成して状態更新関数に渡す必要があります。これは、元のオブジェクトの参照が変わらないと、Reactが状態の変化を検知できず再レンダリングされないためです。
❌ 悪い例(Reactが検知できない)
const [user, setUser] = useState({ name: "Taro", age: 20 });
// 直接プロパティを変更するのはNG
user.age = 21;
setUser(user); // 参照が変わらないため、画面が更新されない
✅ 良い例(新しいオブジェクトを生成する)
const [user, setUser] = useState({ name: "Taro", age: 20 });
// スプレッド構文などを使って、新しいオブジェクトを生成する
setUser({ ...user, age: 21 }); // 画面が正しく更新される
2. 副作用(Side Effects)を扱う:useEffect
useEffect は、データの取得(API通信)、手動でのDOMの操作、イベントリスナーの登録・解除(サブスクリプション)など、レンダリング結果(UIの描画)以外の「副作用(Side Effects)」を実行するためのフックです。
コンポーネントが画面に表示された直後や、特定の値(StateやProps)が変化したタイミングで何かしらの処理を実行したい場合に利用します。
useEffect の基本構文
useEffect は、第一引数に実行したいコールバック関数を、第二引数に**依存配列(Dependency Array)**を渡します。
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 1. 初回マウント時、または userId が変わった時に実行される副作用
setLoading(true);
fetch(`https://api.example.com/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
});
// (オプション) 2. クリーンアップ関数
return () => {
// コンポーネントがアンマウントされる時、またはコンポーネントが再レンダリングされる
// 次のuseEffectの直前に実行されるクリーンアップ処理(例: イベントリスナーの解除など)
console.log('クリーンアップが実行されました');
};
}, [userId]); // 3. 依存配列:この配列の中の値が変化した時だけ、副作用が再実行されます
if (loading) return <p>読み込み中...</p>;
if (!user) return <p>ユーザーが見つかりません</p>;
return <div>{user.name} さんのプロフィール</div>;
}
第二引数(依存配列)の挙動パターン
useEffect の最も重要で、かつ最も開発者がつまずきやすいのが、この「第二引数(依存配列)」の扱いです。以下の3つのパターンを完璧に理解しておくことが、バグを防ぐ鍵となります。
① 第二引数を省略した場合(毎回実行)
useEffect(() => {
console.log("コンポーネントがレンダリングされる度に呼ばれます");
});
※ これはパフォーマンス低下の原因になりやすいので、意図的な場合を除いて避けるべきです。
② 空の配列 [] を渡した場合(初回のみ実行)
useEffect(() => {
console.log("コンポーネントが最初に画面に表示された直後の1回だけ呼ばれます");
// APIからの初期データの取得などによく使われます
}, []);
③ 配列に値を入れた場合(値の変化時に実行)
useEffect(() => {
console.log("count または name の値が前回のレンダリング時から変化した場合のみ呼ばれます");
}, [count, name]);
⚠️ よくある罠:無限ループの恐怖
useEffect の中で useState を使って状態を更新し、その更新した状態を useEffect の依存配列に入れてしまうと、無限ループ(再レンダリング→useEffectの実行→再レンダリング…)が発生し、ブラウザがクラッシュする原因になります。
// ❌ 危険なコード:無限ループ
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // 状態を更新
}, [count]); // 状態の変更を検知してまたuseEffectが呼ばれる
このような場合は、count を依存配列から外すか、状態更新関数(setState)のコールバック引数を使うなどして対処します。
3. その他の便利なHooks
Reactには、useState や useEffect 以外にも強力なHooksがいくつも用意されています。
useContext: コンポーネントツリーの深い階層まで、Propsのバケツリレーをせずにデータを渡す。useRef: DOM要素(inputタグやdivなど)に直接アクセスしたり、再レンダリングをトリガーしない値(タイマーIDなど)を保持する。useMemo/useCallback: 重い計算結果や関数の参照をメモ化(キャッシュ)して、不要な再レンダリングや計算を防ぎ、パフォーマンスを最適化する。
まとめ
useState は「UIの状態」を管理し、useEffect は「外部との通信やDOMの操作など、UI描画以外の副作用」を管理します。この2つのHooksの役割と挙動(特に useEffect の依存配列のルールや、オブジェクトの更新方法)を正しく理解し、使い分けられるようになれば、モダンなReact開発の基本はマスターしたと言えるでしょう。
関数コンポーネントとHooksがもたらすシンプルで強力なReactの恩恵を最大限に活用し、よりスマートで効率的なフロントエンド開発を楽しんでいきましょう!