【これから学ぶ人向け】Reactフックのポイントと具体例

Featured image of the post

概要

Reactのフックを初心者でも雰囲気をつかめるように超ざっくりまとめる😊

💡
正確性よりもニュアンスを理解するための記事です。

フックって何?

関数コンポーネントをパワーアップさせる関数use〇〇を総称してフックという。

とりあえず抑えておきたいポイント
  • React16.8で登場して関数コンポーネントを強化してくれた。
  • クラスコンポーネントでは使えない。
  • 多数のフック関数use〇〇があり、関数コンポーネントパワーアップさせてくれる。(後述)

フックのいいところ

(フック登場前)

複雑な機能(状態管理など)が関数コンポーネントでは使えなかった🥲

→代わりにクラスコンポーネントで書いていた。

(フック登場後)

複雑な機能(状態管理など)が関数コンポーネントで使えるようになった😊

→関数コンポーネントでほとんど書けるようになった。

結論
  • フックのおかげで関数コンポーネントでできることが増えた!
  • フック登場後はあまりクラスコンポーネントを使わなくなった!

フックの一覧早見表

代表的なフックの一覧

フック役割使用例重要度
useStateコンポーネントの状態を管理するためのフックUIのトグル状態(開閉など)の管理
useEffectコンポーネントに追加の処理(データ取得、イベントリスナー設定など)を加えるフック特定のタイミングでAPIからデータを取得
useContextアプリ全体で共有されているデータに簡単にアクセスできるようにするフックテーマや言語設定の設定を取得
useReducer複雑なデータ管理を行うフックフォーム入力の複合的なバリデーション
useCallbackメモ化されたコールバック関数を生成するフックイベントハンドラの再生性を防ぐ
useMemoメモ化された値を生成するフック不要な計算処理の実行を防ぐ
useRef特定のDOM要素などの参照を保持するフック
(getElementByIdのReact版)
特定のDOM要素のサイズを計測する。
カスタムフック自分で定義するフックデータ取得処理を関数化して複数コンポーネントで使い回す。
useImperativeHandle子コンポーネントから親コンポーネントへ特定の機能や値を提供するフック親コンポーネントから子コンポーネントのアニメーションを開始・停止する。
useLayoutEffect画面を再描画する直前に、副作用(DOM要素のサイズ計算など)を行うフック。再描画時のちらつきを防ぐ。
useDebugValueカスタムフックのデバッグ用フック

フックを1つずつ解説

✅それぞれのフックが何をするものか簡潔にまとめる。

useState(重要度:高)
特徴

コンポーネントの状態を管理するためのフック。

使用シチュエーション
  • 入力フォームの入力状態(テキスト入力など)を管理
  • UIのトグル状態(開閉など)の管理

サンプルコード

const [count, setCount] = useState(0);

具体例

ユーザーのクリック回数をカウントし、表示する。

import React, { useState } from 'react';

function ClickCounter() {
    // 状態変数「count」と、その状態を更新するための関数「setCount」を定義する。
    // 引数は「count」の初期値になる。0を指定する。
    const [count, setCount] = useState(0);

    return (
        <div>
            <p>You clicked {count} times</p>
            { /*ボタンがクリックされたとき、countの値を1増やす。*/ }
            <button onClick={() => setCount(count + 1)}>
                Click me
            </button>
        </div>
    );
}

export default ClickCounter;

💡
このようにコンポーネントの可変となる部分にuseStateを使う!

useEffect(重要度:高)
特徴

コンポーネントに追加の処理(データ取得、イベントリスナー設定など)を加えるフック。

初回レンダリング時、再レンダリング時などに処理を実行させることができる。

使用シチュエーション
  • 初回レンダリング時にAPIからデータを取得
  • 再レンダリング時にウィンドウサイズの変更を監視

サンプルコード

useEffect(() => { fetchData().then(data => setData(data)); }, []);

具体例

初回レンダリング時に、コンソールにメッセージを表示する。

import React, { useEffect } from 'react';

function ComponentWithEffect() {
		// 第一引数には、実行したい関数を定義する。
    // 第二引数には、依存する値のリストを定義し、いつ関数が実行されるか定義する。
		// ここでは空の配列を渡しているので(依存なし)、初回レンダリング時に一度だけ実行される。
    useEffect(() => {
        console.log('Component has mounted!');
    }, []);

    return <div>Hello, World!</div>;
}

export default ComponentWithEffect;

💡
動的な処理を実装するのにuseEffectが使える!

useContext(重要度:中)
特徴

アプリ全体で共有されているデータ(テーマの設定やユーザー情報など)を、個々のコンポーネントで簡単に利用するためのフック。

使用シチュエーション
  • テーマや言語設定
  • ユーザー認証の状態

サンプルコード

const theme = useContext(ThemeContext);

具体例

テーマの設定をアプリ全体で共有し、表示する。

import React, { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

function ThemeViewer() {
    // ThemeContextから現在のテーマ(ここでは色)を取得する。
    const theme = useContext(ThemeContext);

    return (
        // 取得したテーマの色をスタイルとして適用する。
        <div style={{ color: theme.color }}>
            Current theme color is {theme.color}
        </div>
    );
}

export default ThemeViewer;

【補足】事前にReact.createContextでコンテキストを作成しておく必要がある

useContext事前に定義されたコンテキストを使うもの。

そのため事前にReact.createContextでコンテキストを作成しておく必要がある。

例:事前にテーマの色を定義しておく

import React from 'react';

// テーマの色を定義
const defaultTheme = {
    color: 'blue'
};

// React.createContextを使用してコンテキストを作成する
export const ThemeContext = React.createContext(defaultTheme);

💡
グローバルな設定を取得するのにuseContextが便利!

useReducer(重要度:中)
特徴

複数の状態を組み合わせたり、一連のアクションに応じて状態を更新するなど、複雑なデータ管理を行うフック。

使用シチュエーション
  • ショッピングカートやゲームの状態管理
  • フォーム入力の複合的なバリデーション

サンプルコード

const [cart, dispatch] = useReducer(cartReducer, initialCart);

具体例

カウンターの値を増減させる。

import React, { useReducer } from 'react';

// useReducerで使う。
// この関数は「現在の状態」と「アクション」を引数に取り、「新しい状態」を返す。
const reducer = (state, action) => {
    switch (action.type) {
        case 'increment':
            return { count: state.count + 1 };
        case 'decrement':
            return { count: state.count - 1 };
        default:
            throw new Error();
    }
};

const Counter = () => {
    // useReducerフックを使用して、「reducer関数」と「初期状態」を渡す。
    // これにより、「状態」と「その状態を更新するためのdispatch関数」が返される。
    const [state, dispatch] = useReducer(reducer, { count: 0 });

    // dispatch({ type: 'increment' })を呼び出すと、reducer関数が呼び出され、
    // 「現在の状態(例:1)」と「アクション(例:increment)」が引数として渡される。
    // reducer関数は、「新しい状態(例:2)」を返す。
    return (
        <div>
            Count: {state.count}
            <button onClick={() => dispatch({ type: 'increment' })}>+1</button>
            <button onClick={() => dispatch({ type: 'decrement' })}>-1</button>
        </div>
    );
};

export default Counter;

💡
複雑なコンポーネントの状態管理にuseReducerが便利!

useCallback(重要度:中)
特徴

メモ化されたコールバック関数を生成するフック。

「レンダリングのたびに中身が変わらない関数」をメモ化(再生成防止)してパフォーマンスを向上させる。

使用シチュエーション
  • クリックされたとき「こんにちは!」と表示するだけのボタンがある。

    この「こんにちは!」と表示する関数をメモ化する。

    (関数は常に「こんにちは!」を表示するだけなので再生成不要→パフォーマンスが向上する)

サンプルコード

const handleClick = useCallback(() => console.log('こんにちは!');, []);

具体例

ボタンクリック時のイベントハンドラをメモ化する。

(コンポーネントが再レンダリングされてもイベントハンドラを再生成しなくなる)

import React, { useCallback } from 'react';

function SimpleButton() {
    // 初回レンダリング時のみ関数が生成される
    const handleClick = useCallback(() => {
        console.log('こんにちは!');
    }, []);

    return <button onClick={handleClick}>Click me</button>;
}

export default SimpleButton;

💡
パフォーマンスの向上にuseCallbackを使う!

useMemo(重要度:中)
特徴

メモ化された値を生成するフック。

(値だけでなく関数など、なんでもメモ化できる。)

使用シチュエーション
  • 計算コストが高い操作(フィルタリング、ソートなど)がある。

    その実行結果をメモ化する。

    (計算結果が変わらない場合は再実行不要→パフォーマンスが向上する)

サンプルコード

const sortedList = useMemo(() => sortList(list), [list]);

具体例

数字の二乗の計算結果をメモ化する。

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

function SquaredNumber() {
    const [number, setNumber] = useState(0);

    // useMemoを使用して数値の二乗を計算し、その結果をメモ化する。
    // 依存配列にnumberを指定しているため、numberが変更されたときのみ再計算される。
    const squared = useMemo(() => number * number, [number]);

    return (
        <div>
            <input
                type="number"
                value={number}
                onChange={e => setNumber(parseInt(e.target.value, 10))}
            />
            <p>二乗した値: {squared}</p>
        </div>
    );
}

export default SquaredNumber

💡
パフォーマンスの向上にuseMemoを使う!

useRef(重要度:中)
特徴

特定のDOM要素や任意のデータ値への「リンク」(参照)を作成し、それを保持するためのフック。(getElementByIdのReact版)

これにより、DOM要素に直接アクセスしたり、コンポーネントの再レンダリングで変化しない値を保持することができる。

使用シチュエーション(DOM要素への直接アクセス)
  • ボタンクリックで特定のテキスト入力フィールドにフォーカスを設定する。
  • DOM要素に直接アニメーションを適用する。
  • 特定のDOM要素のサイズや画面上の位置を計測する。

使用シチュエーション(コンポーネント内での値の保持
  • 再レンダリングされても変化しない値(例えば、setIntervalのID)を保持する。
  • 以前のレンダリング時の状態やプロップの値を保存しておく。
  • 複数のレンダリング間で共有される値を管理する。

サンプルコード

const inputRef = useRef();

具体例

初回レンダリング時、入力フォームにフォーカスを設定する。

import React, { useRef, useEffect } from 'react';

function AutoFocusedInput() {
    // 参照を作成
    const inputRef = useRef();

    useEffect(() => {
        // input要素への参照を使用して、その要素にフォーカスを当てる。
        inputRef.current.focus();
    }, []);

    // input要素に参照を割り当てる
    return <input ref={inputRef} type="text" />;
}

export default AutoFocusedInput;

💡
DOM要素のアクセス値の保持にuseRefを使う!

カスタムフック(重要度:中)
特徴

自分で定義するフック。

use〇〇という名前の独自の関数のことをカスタムフックという。

使用シチュエーション

他のフック(useState, useEffectなど)を使った処理を関数化するときに使う。

  • データ取得処理を関数化して複数コンポーネントで使い回す。
  • アニメーションを関数化して複数コンポーネントで使い回す。

サンプルコード

function useHogeHook(){ … }

具体例

データ取得のカスタムフック(独自の関数)を定義

useFetchUserData.js

import { useState, useEffect } from 'react';

// ユーザーデータを取得するカスタムフック
function useFetchUserData() {
  const [userData, setUserData] = useState(null);

  useEffect(() => {
    // fetchUserData() は外部APIからユーザーデータを取得する架空の関数
    fetchUserData().then(data => setUserData(data));
  }, []);

  return userData;
}

export default useFetchUserData;

定義したカスタムフック(独自の関数)を使う

MyComponent.jsx

import React from 'react';
import useFetchUserData from './useFetchUserData'; // カスタムフックをインポート

// ユーザーデータを表示するコンポーネント
function MyComponent() 
	// カスタムフック(データ取得処理)を実行
  const userData = useFetchUserData();

  return (
    <div>
      <h1>User Data</h1>
      <p>{JSON.stringify(userData)}</p>
    </div>
  );
}

export default MyComponent;

💡
関数化することで複数のコンポーネントで処理が使い回せて便利!


💡
以降はあまり重要でないので読み飛ばしてもOK

useImperativeHandle(重要度:低)
特徴

子コンポーネントから親コンポーネントへ特定の機能や値を提供するフック。

(親から子の関数などが使える)

使用シチュエーション
  • 親コンポーネントから子コンポーネントのアニメーションを開始・停止する。
💡
useImperativeHandleは特殊なケースでのみ使用する。

Reactでは親コンポーネントから子コンポーネントのフローが基本。

サンプルコード

useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } }));

具体例

親コンポーネントから子コンポーネントの関数を直接呼び出す。

import React, { useRef, useImperativeHandle, forwardRef } from 'react';

// 子コンポーネント
const ChildComponent = forwardRef((props, ref) => {
    useImperativeHandle(ref, () => ({
        // 3. このメソッドは親コンポーネントから直接呼び出すことができる
        customMethod() {
            console.log('customMethodが呼び出されました');
        }
    }));

    return <div>子コンポーネント</div>;
});

// 親コンポーネント
const ParentComponent = () => {
		// 1. 子コンポーネントの参照を作成
    const childRef = useRef();

    const handleClick = () => {
        // 4. 子コンポーネントのcustomMethodを直接呼び出す
        childRef.current.customMethod();
    };

    return (
        <div>
						{/* 2. 子コンポーネントの参照を渡す */}
            <ChildComponent ref={childRef} />
            <button onClick={handleClick}>子コンポーネントのメソッドを呼び出す</button>
        </div>
    );
};

export default ParentComponent;

【補足】useImperativeHandleuseRefと組み合わせて使う

以下の手順で使う。

  1. 親コンポーネントで useRef を使って参照(ref)を作成する。
  2. 参照を子コンポーネントに渡す。
  3. 子コンポーネント内で useImperativeHandle を使い、この参照を通じて親コンポーネントに公開したい関数や値を定義する。
  4. 親コンポーネントで子コンポーネントの関数や値を使う。

💡
どうしても親コンポーネントから子コンポーネントの処理を呼び出したいときにuseImperativeHandleが便利!

useLayoutEffect(重要度:低)
特徴

画面を再描画する直前に、副作用(DOM要素のサイズ計算など)を行うフック。

使用シチュエーション
  • 描画前の特定の要素のサイズを計測し、それに基づいてレイアウトの調整を行う。
  • 再描画時のちらつきを防ぐ。

サンプルコード

useLayoutEffect(() => { measurePosition(node); }, [node]);

具体例

コンポーネントのサイズを計測する。

import React, { useLayoutEffect, useRef, useState } from 'react';

function MeasureComponent() {
    const [size, setSize] = useState({});
    const ref = useRef();

    // 同期的にブラウザの描画の前に実行される。
    // そのため画面のちらつきを防ぐことができるがパフォーマンスが悪くなる。
    useLayoutEffect(() => {
        setSize({
            width: ref.current.offsetWidth,
            height: ref.current.offsetHeight
        });
    }, []);

    return (
        <div ref={ref}>
            <p>Width: {size.width}px, Height: {size.height}px</p>
        </div>
    );
}

export default MeasureComponent;

💡
描画前に何か処理をしたい場合にuseLayoutEffectを使う!

useDebugValue(重要度:低)
特徴

カスタムフックのデバッグ用フック

💡
使用するにはブラウザの拡張機能「React Developer Tools」が必須!

使用シチュエーション
  • 複雑なカスタムフックの処理をデバッグしたいとき
  • 複数のインスタンスで同じカスタムフックを使っていて、各インスタンスの状態を個別に確認したいとき

サンプルコード

useDebugValue(isOnline ? 'Online' : 'Offline');

具体例

カスタムフックの変数の状態を確認する。

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

function useCustomHook() {
    const [value, setValue] = useState('これは初期値です');

    // React Developer Toolsにデバッグ情報を表示する。
    // valueが設定されている場合は'値はセットされています'、設定されていない場合は'値はセットされていません'と表示する。
    useDebugValue(value ? '値はセットされています' : '値はセットされていません');

    return value;
}

function Test() {
    const value = useCustomHook();

    return <div>{value}</div>;
}

export default Test;

「React Developer Tools」で確認した結果

Image in a image block

💡
複雑なカスタムフックのデバッグにuseDebugValueが便利!

気になった点のメモ

useCallbackとuseMemoは何が違う?

メモ化する対象が異なる。

useCallback
  • 関数をメモ化

useMemo
  • 値や関数など任意のものをメモ化(汎用的)

💡
useCallbackは関数のメモ化を簡単に書くためのもの。

【補足】useCallbackuseMemo の書き換えが可能!

useCallbackで書いた場合

const memoizedCallback = useCallback(() => {
    doSomething(a, b);
}, [a, b]);

useMemoで書き換えた場合

const memoizedCallback = useMemo(() => {
    return () => {
        doSomething(a, b);
    };
}, [a, b]);

💡
useMemoだと長くなってしまうのでuseCallbackを使った方が楽!

とりあえず全部の変数や関数にuseCallback、useMemoを付けていい?

✅必要なとき(計算量が多いときなど)だけ使うのがベストだが、初心者のうちは全部つけてもOK。

そもそもuseMemouseCallback自体にメモリと計算コストが伴うことは覚えておきたい。

全部にuseMemo、useCallbackを付けるメリット
  • 理解の向上

    実際に使ってみることで、これらのフックがどのように動作するかを深く理解できる。特に、依存関係を意識しながら開発できるのがメリット◎

  • パフォーマンスへの意識

    常にuseMemouseCallbackを使うことで、最適なパフォーマンスへの意識が高まる。

全部にuseMemo、useCallbackを付けるデメリット
  • パフォーマンスの低下の恐れ

    useMemouseCallback自体にもメモリと計算コストが伴う。不必要な場合は逆にパフォーマンスが(少しだけ)悪くなる。

  • 可読性の低下

    コードが長くなる。初心者は特に読みづらく感じる可能性がある。

💡
パフォーマンスが向上するときだけ使うのがベストだが、その判断も難しいので勉強も兼ねて全部にuseMemo、useCallbackを付けるのもあり。

ただし逆にパフォーマンスが悪くなる可能性があることを覚えておく!

useEffectとuseLayoutEffectは何が違う?

実行されるタイミング、同期・非同期が異なる

useEffect
  • レンダリングが完了した後
  • 非同期的に実行される

useLayoutEffect
  • ブラウザの描画前
  • 同期的に実行される

💡
useEffectの方がパフォーマンスがいいので、基本はuseEffectを使う。

参考サイト