Reactの関数コンポーネントに機能(状態管理、ライフサイクルなど)を持たせるために登場したのが Hooks(フック) です。ここでは、特に重要な8つのHooksとその実用例を解説します。
🎣 Hooks とは
Reactの機能を関数コンポーネント内で使えるようにした仕組みです。
use から始まる関数で、状態管理・副作用・メモ化などを扱います。
1. useState — 状態管理
コンポーネント内でデータを保持・更新するための最も基本的なHookです。
これを使わずに変数を書き換えてもUIは更新されません。
- 役割: UIのレンダリングに必要な「状態(State)」を定義し、その更新を管理します。
-
返り値:
[現在の値, 値を更新する関数]のペア。
例:
// 0 を初期値として設定
const [count, setCount] = useState(0);
// 状態を更新するときはセッター関数を使う
setCount(count + 1);
2. useEffect — 副作用の実行
コンポーネントのレンダリング後に発生する処理(副作用、Side Effect)を実行するためのHookです。API通信、DOM操作、タイマー設定などが該当します。
- 役割: 初回表示時、または指定した依存値が変化したタイミングで処理を実行します。
-
第二引数(依存配列):
-
[](空の配列): 初回レンダリング時のみ実行(componentDidMountに近い)。 -
[count](値):countが変化した時に実行(componentDidUpdateに近い)。 - 省略: レンダリングのたびに毎回実行。
-
例:
useEffect(() => {
// 依存配列に [count] があるため、countが変化した時に実行される
console.log("countが変化した時に実行");
// クリーンアップ関数(コンポーネントが消える際や、次回実行前に呼ばれる)
return () => {
console.log("前のエフェクトをクリーンアップ");
};
}, [count]);
3. useSWR — データ取得とキャッシュ
データ取得に useEffect と fetch を使う場合、ローディング、エラー、キャッシュ、再取得といった複雑な処理をすべて手動で管理する必要があります。
useSWR は、その面倒な処理を肩代わりするライブラリであり、API通信におけるベストプラクティスを提供します。
- SWR: Stale-While-Revalidate(古くなったデータを利用しながら、裏側で新しいデータを再検証する)の略。
-
メリット:
- 自動キャッシュ: 取得したデータを自動でキャッシュします。
- 重複排除: 同じ SWR キー(URLなど)で複数回呼んでも、APIリクエストは1回のみにまとめられます。
- 自動リバリデート: 画面にフォーカスが戻った時や、ネットワークに再接続した時に自動で最新データを取得します。
例:
import useSWR from "swr";
const fetcher = (url) => fetch(url).then((res) => res.json());
export default function Component() {
// data, error, isLoading の状態を自動で管理
const { data, error, isLoading } = useSWR("/api/user", fetcher);
if (isLoading) return <p>読み込み中...</p>;
if (error) return <p>エラーが発生しました</p>;
return <div>{data.name}</div>;
}
4. useContext — グローバルな値共有
離れたコンポーネント間で値を渡す際に、中間のコンポーネントを経由してpropsをバケツリレーする Props地獄 を回避します。
- 役割: アプリケーション全体やコンポーネントツリーの深い場所にあるコンポーネントに、テーマ設定や認証情報などの値を直接渡すために使います。
-
注意点: 非同期データの共有は、次シリーズで解説する
useフックやTanStack Queryを使うのが現在の主流です。
例:
// 1. Contextを作成
const UserContext = createContext();
// 2. 値を受け取る
const value = useContext(UserContext);
5. useRef — 値の参照・DOM参照
値を保持したいが、その値が変更されてもレンダリング(再描画)を引き起こしたくない場合に使用します。また、DOM要素を直接参照するためにも使われます。
-
役割:
- レンダリングされない値(タイマーIDなど)の保持。
- 入力フォームなどのDOM要素への直接アクセス。
例:
// 初期値を null で設定
const inputRef = useRef(null);
// DOMがレンダリングされた後、currentプロパティを通してアクセス
// 入力フォームにフォーカスを当てる
inputRef.current.focus();
6. useReducer — 複雑な状態管理
useStateよりも複雑なロジックで状態を更新する場合や、複数の状態をまとめて管理したい場合に便利です。Reduxに近い考え方です。
- 役割: 現在の状態と「アクション」を受け取って新しい状態を返す「リデューサー関数」を使い、状態の更新を一元管理します。
-
返り値:
[現在の状態, ディスパッチ関数]のペア。
例:
const [state, dispatch] = useReducer(reducer, initialState);
// アクションをディスパッチすることで状態を更新する
dispatch({ type: "increment" });
7. useMemo / useCallback — メモ化によるパフォーマンス最適化
重い処理の結果や、不必要に関数が再生成されるのを防ぎたい場合に、値をキャッシュして再利用します(メモ化)。
useMemo — 値のメモ化
計算コストが高い処理結果をキャッシュします。依存値が変わらない限り再計算されません。
例:
// heavyCalc(num) の結果をキャッシュ。numが変わらなければ、関数が再実行されても再計算されない。
const result = useMemo(() => heavyCalc(num), [num]);
useCallback — 関数のメモ化
useMemo が「値」をメモ化するのに対し、useCallback は「関数定義」自体をキャッシュします。
関数をpropsとして子コンポーネントに渡す際、不要な再レンダリングを防ぐために使用されます。
例:
// valueが変わらない限り、handleClick関数は同じインスタンスを使い回す
const handleClick = useCallback(() => doSomething(), [value]);
🧩 カスタムHook
Hooksを組み合わせた独自のロジックや状態管理の処理を useXXX という関数として切り出すことができます。これにより、コンポーネント間でロジックを簡潔に再利用できるようになります。
例:
// useCounter.js
export const useCounter = () => {
const [count, setCount] = useState(0);
const increment = () => setCount(c => c + 1);
return { count, increment };
};