React Hooks — なぜか、主なフックと使用ルール

著者: IT Sectr 公開日: 2026-07-04 読了時間: 10 分

React Hooks は、クラスを書かずに関数コンポーネントでステート、イフェクト、コンテキスト、および React の他の機能を使用できるようにする関数です。React 16.8 で導入されたフックは、HOC や render props といった複雑なパターンを置き換え、開発アプローチを根本的に変えました。React, 2024によると、15つの組み込みフックがあり、すべてのコンポーネント管理シナリオをカバーします。

まとめ

  • フック — extends Component なしで関数コンポーネントに React 機能を接続するための関数。
  • useState — 状態管理のための基本フック、値とセッターを返します。
  • useEffect — 副作用のため: API リクエスト、サブスクリプション、タイマー、DOM 操作。
  • useContext — Consumer ラッパーや static contextType なしで React コンテキストにアクセスするため。
  • フックのルール — トップレベルでのみ、および関数コンポーネントまたはカスタムフックの内部でのみ呼び出します。

React Hooks とは?

React Hooks は、クラスを作らずにフレームワークの内部メカニズムに“フックする”ことができる関数のセットです。React 16.8 以前は、関数コンポーネントは props を受け取って JSX を返すことしかできませんでした — 複雑なロジックにはクラスコンポーネントが必要でした。

クラスコンポーネントの問題は関連するロジックの分割です。同じエンティティに関連するコード(例えば、WebSocket サブスクリプション)は、componentDidMount、componentDidUpdate、componentWillUnmount の間に強制的に分けられていました。フックはこれを解決します: すべてのロジックが1つの useEffect にまとめられます。

2つ目の問題はロジックの再利用です。クラスコンポーネント間で状態ロジックを再利用するには、HOC(高層コンポーネント)または render props を適用する必要がありました — どちらのパターンもコンポーネントツリーに余計なネストを作りました。カスタムフックはネストなしでこれを解決します。

useState — 状態管理

useState は最も基本的なフックです。初期値を受け取り、タプルを返します: 現在の値とそれを更新する関数です。クラスの this.setState とは異なり、useState はオブジェクトをマージしません — 新しい状態は、特定の useState 呼び出しに対して前の状態を完全に置き換えます。

js
import React, { useState } from 'react';

function Form() {
  const [text, setText] = useState('');
  const [submitting, setSubmitting] = useState(false);

  async function handleSubmit() {
    setSubmitting(true);
    await fetch('/api/submit', {
      method: 'POST',
      body: JSON.stringify({ text })
    });
    setSubmitting(false);
  }

  return (
    <div>
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button onClick={handleSubmit}
        disabled={submitting}>
        {submitting ? 'Saving...' : 'Submit'}
      </button>
    </div>
  );
}

Functional setState フォーム

useState のセッターは、前の値を受け取って新しい値を返す関数を受け入れることができます。関数形式は、新しい状態が前の状態から計算され、バッチ化された setState 呼び出しが可能な場合に必要です — 各呼び出しは現在の前の値を受け取ります。

useEffect — 副作用とライフサイクル

useEffect は、関数コンポーネントで副作用を実行するためのフックです。2つの引数を受け取ります: イフェクト関数と依存配列です。React は各レンダリング後にイフェクトを実行しますが、依存関係が指定されている場合は — そのうちのいずれかが変わったときだけ実行されます。

空の依存配列 [] は、イフェクトが最初のレンダリング後に1度だけ実行されることを意味します(componentDidMount に相当)。イフェクトから関数を返すと、それはアンマウント時(componentWillUnmount に相当)およびイフェクトの再実行前に実行されるクリーンアップです。

js
import React, { useState, useEffect } from 'react';

function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const socket = new WebSocket(`wss://chat/${roomId}`);

    socket.onmessage = (event) => {
      setMessages(prev => [...prev, JSON.parse(event.data)]);
    };

    // クリーンアップ - ソケットを閉じる
    return () => socket.close();
  }, [roomId]);

  return <ul>
    {messages.map((msg, i) =>
      <li key={i}>{msg.text}</li>
    )}
  </ul>;
}

useEffect の依存関係は、イフェクトと外部との唯一の接点です。イフェクトが props または状態から変数を使用する場合、その変数は依存配列に入れる必要があります。このルールを無視すると、ステールクロージャー(stale closure)によるバグの原因になります。

useContext — コンテキストへのアクセス

useContext は、コンテキスト値を読み取るためのフックです。React.createContext で作成されたコンテキストオブジェクトを受け取り、現在の値を返します。コンテキストが変わると、コンポーネントは自動的に新しい値で再レンダリングされます。

useContext 以前は、関数コンポーネントでコンテキストにアクセスするには Context.Consumer でラップする必要があり、それによってネストが生じました。useContext は Consumer を完全に削除します — 関数コンポーネントのどこででもフックを呼ぶだけです。

js
import React, { createContext, useContext } from 'react';

const ThemeContext = createContext('ライト');

function ThemedButton() {
  const theme = useContext(ThemeContext);

  return <button
    className={`btn btn-${theme}`}
  >
    {theme === 'dark' ? 'moon' : '☀️'}
  </button>;
}

useContext は Redux やグローバル状態の代替ではありません。React はあまり変更されない値— テーマ、ロケール、認証ステータスにコンテキストを使用することを推奨しています。頻繁に更新されるデータの場合、再レンダリングごとにすべてのコンテキストコンシューマーが再計算され、パフォーマンスが低下する可能性があります。

useReducer — 複雑な状態ロジック

useReducer は、複雑な更新ロジックをもつ状態に対する useState の代替です。リデューサー関数と初期状態を受け取り、現在の状態と dispatch 関数を返します。リデューサーは現在の状態とアクションを受け取り、新しい状態を返します — 副作用のない純掟関数です。

useReducer は、複数のアクション(set, reset, increment, addItem)が1つの状態を管理する場合に特に便利です。異なるセッターをもつ複数の useState の代わりに — 明確なアクションタイプをもつ1つのリデューサーです。これにより、ロジックが予測可能で、テストが簡単になります。

js
import React, { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 };
    case 'setStep':
      return { ...state, step: action.step };
    case 'reset':
      return { count: 0, step: 1 };
    default:
      throw new Error('Unknown action: ' + action.type);
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, {
    count: 0,
    step: 1
  });

  return (
    <>
      <p>Count: {state.count}</p>
      <button
        onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button
        onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </>
  );
}

useRef, useMemo, useCallback — 最適化

useRef は、レンダリング間で継続する可変オブジェクトを作成します。current を変更しても再レンダリングは発生しません。主な用途は、DOM 要素へのアクセスと可変な値(タイマー、前の値、ライブラリインスタンス)の保存です。

useMemo は関数の結果をメモ化します。依存関係が変わったときだけ値を再計算します。費時のかかる計算に使用します — 大きな配列のフィルタリング、ソート、データのフォーマットなど。useCallback は、関数をメモ化するための useMemo の特別なケースです。

js
import React, { useRef, useMemo, useCallback } from 'react';

function SearchPage({ items, query, onItemSelect }) {
  const inputRef = useRef(null);

  // マウント時に自動フォーカス
  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  // フィルタリング結果をメモ化
  const filtered = useMemo(() => {
    return items.filter(item =>
      item.name.toLowerCase()
        .includes(query.toLowerCase())
    );
  }, [items, query]);

  // コールバックをメモ化
  const handleSelect = useCallback((id) => {
    onItemSelect(id);
  }, [onItemSelect]);

  return (
    <div>
      <input ref={inputRef}
        value={query}
        onChange={e => setQuery(e.target.value)}
      />
      <ul>
        {filtered.map(item =>
          <li key={item.id}>
            <button
              onClick={() => handleSelect(item.id)}>
              {item.name}
            </button>
          </li>
        )}
      </ul>
    </div>
  );
}

重要なルール: useRef、useMemo、useCallback は最適化ツールであり、必須構造ではありません。すべてを useMemo で囲まないでください — まずパフォーマンスを測定してください。過剰な最適化はコードを複雑にします。React はコンポーネントを高速にレンダリングします; useMemo は計算が本当に費時のかかる場合(O(n²) 以上)にのみ必要です。

フックのルールとよくある誤り

React はフックについて2つの厳密なルールを導入しました。ひとつ目: フックはトップレベルでのみ呼び出します — 条件分岐、ループ、またはネスト関数の内部では呼び出さないでください。ふたつ目: フックは React 関数コンポーネントまたはカスタムフックからのみ呼び出します。ESLint プラグイン eslint-plugin-react-hooks が自動的に両方のルールをチェックします。

よくある誤りはフック呼び出しの順序の違反です。React はレンダリング間のフック呼び出しの順序に依存しています。フックが条件分岐の内部にあり、あるレンダリングで実行されなかった場合、それに続くすべてのフックが屈みすれ、バグの原因となります。

誤り発生すること解決策
条件内のフックフックの順序がめくれる条件ロジックをフック内に移動
Stale closureイフェクトが古い値を使用すべての依存関係を配列に追加
無限ループ依存関係なしの useEffect が state を変更依存関係を指定または useReducer を使用
レンダーでの ref 変更関数ボディでの副作用変更を useEffect に移動

2番目のよくある誤りは、useEffect でのstale closureです。イフェクトが依存関係に指定されていない変数を使用する場合、イフェクトはクロージャー作成時の値を“キャプチャ”します。解決策は、使用されているすべての変数を依存配列に必ず含めることです。依存関係が多すぎる場合は、イフェクトを複数に分割することを検討してください。

よくある質問

なぜフックを条件分岐内で呼べないのか?

React は、レンダリング間のフック呼び出しの順序に依存しています。フックが条件分岐内で呼び出され、あるレンダリングで実行されなかった場合、それに続くすべてのフックが屈みすれ、状態が混乱します。ESLint ルール hooks/exhaustive-deps がこれについて警告します。

useEffect と useLayoutEffect の違いは?

useLayoutEffect は同期で、ブラウザが変更をペイントする前に実行されます。useEffect は非同期で、ペイント後に実行されます。useLayoutEffect は、ユーザーが変更を見る前に DOM を測定または変更する必要がある場合(ポプオーバー位置、スクロール復元)にのみ使用します。

アンマウント時に fetch リクエストをキャンセルするには?

useEffect 内でAbortControllerを使用します。controller を作成し、signal を fetch に渡し、クリーンアップ関数で controller.abort() を呼びます。古いブラウザでは、cancelled フラグを使用します: return () => { cancelled = true; } setState の前にチェックを行います。

カスタムフックとは何か、いつ作成するのか?

カスタムフックは、組み込みフックを使用する、use 接頭辞がついた JavaScript 関数です。同じフックを使用したロジックが2つ以上のコンポーネントで繰り返される場合に作成します。例: useDebounce, useLocalStorage, useMediaQuery。カスタムフックは HOC や render props を置き換えます。

Strict Mode で useEffect が2回実行されるのはなぜ?

Strict Modeでは、React が開発環境でコンポーネントを意図的に2回マウント・アンマウントし、未クリーンのイフェクトによるバグを見つけます。クリーンアップが正しく書かれていれば、2回の呼び出しは問題を引き起こしません。プロダクションモードではこれは発生しません。

まとめ

  • React Hooks — クラスを置き換えて、関数コンポーネントで状態、イフェクト、コンテキストを使用するための関数。
  • useState — 安全な更新のための関数形式セッターをもつローカル状態のための基本フック。
  • useEffect — 依存配列とクリーンアップ関数をもつ副作用のための一般的なフック。
  • useContext — Consumer ラッパーと余計なネストなしで React コンテキストへの直接アクセス。
  • useReducer — 単一コンポーネント内での Redux の代替として、アクションタイプをもつ複雑な状態ロジックのため。
  • useRef, useMemo, useCallback — 最適化ツール: DOM 参照、値とコールバックのメモ化。
  • トップレベルルール — 条件、ループ、ネスト関数の外でフックを呼ぶことの厳密な制限。

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください