1. ホーム
  2. 記事一覧
  3. Reactの基本、カスタムフックを理解しよう

2024.10.31

Reactの基本、カスタムフックを理解しよう

はじめに

Reactは、ユーザーインターフェースを作るためのJavaScriptライブラリで、ウェブやモバイルアプリの開発で広く利用されています。Reactを学ぶことで、モダンなウェブ開発スキルが身につき、フロントエンドエンジニアを目指す方にとって重要な技術です。

フロントエンド初学者にとって、React Hooksは難しく感じられるかもしれません。カスタムフックもその一つですが、Reactプロジェクトの開発効率を向上させるため、理解しておくと今後の開発で役立ちます。

この記事では、初学者の方が理解しやすいように、シンプルなコード例とともにカスタムフックについて詳しく解説します。カスタムフックは、アプリケーションに合わせた独自のフックを作成できる便利な仕組みです。それでは、一緒に学んでいきましょう!

この記事について

目的

  • カスタムフックの仕組みを理解し、使えるようになること

対象者

  • Reactの学習をしている方
  • カスタムフックの理解を深めたい方

Reactの基本シリーズ

Reactを学んでいる中で、「なかなか理解が進まない…」と感じている方もいるのではないでしょうか?そんな方に向けて、Reactの基本をわかりやすく紹介するシリーズ記事を用意しています。

以下はシリーズの一覧です。この記事とあわせて読むことで、より理解が深まると思いますので、ぜひ参考にしてみてください。

カスタムフックとは

Reactのカスタムフックは、Reactに搭載されている標準フックの機能を組み合わせて、オリジナルの処理を持つフックを作成できる仕組みです。これにより、アプリケーション内で共通する機能を1つにまとめたり、新たな独自の機能を追加することができ、複数のコンポーネントで再利用しやすくなります。

カスタムフックを使用するメリット

  • 同じ処理内容の機能を1つにまとめる

    例えば、Reactアプリケーションの開発中に同じ機能を複数のコンポーネントで使いたい場合、その機能をカスタムフックにまとめることができます。これにより、複数のコンポーネントで同じコードを書く必要がなくなり、コードの再利用性が向上します。機能が1つにまとまることで管理が楽になり、変更が必要になった場合もカスタムフックのみ修正すれば良いので、メンテナンス性が高まります。

  • コンポーネントのViewとロジックを分離

    コンポーネント内にuseStateなどで状態管理のロジックを書くことが多いですが、機能(ロジック)が増えると、コンポーネントの役割が複雑化し、コードの見通しが悪くなることがあります。そこで、カスタムフックを利用して機能(ロジック)とUI部分(View)を分離します。これにより、コードがスッキリして読みやすくなり、コンポーネントの役割が明確になります。

カスタムフックのルール

カスタムフックは開発者が柔軟に作成できるフックですが、作成には以下の2つのルールがあります。

  • Reactのフックを含む関数であること

    カスタムフックは、その内部でuseStateuseEffectなど、Reactの組み込みフックを呼び出します。

  • フック名は「use」で始める

    カスタムフックの名前は必ずuseで始めます。これは、Reactがその関数をフックと認識し、フックの使い方に誤りがある場合に警告してくれるようにするためです。

フックを呼び出さない関数はフックである必要はなく、通常の関数として扱い、名前も通常の関数名にします。この命名ルールはReactが必須としているわけではありませんが、関数がフックを含むかどうかを明確にするために、命名の規則に従うことが推奨されます。

カスタムフック名に「use」をつけることで、Reactにその関数がフックであることを認識させます。これにより、他のコンポーネント内で条件分岐内からカスタムフックを呼び出せなくなり、不具合を防止できます。

Reactでは「フックを呼び出すのは関数のトップレベルだけ」というルールがあります。条件分岐内でフックを呼び出すと、レンダリングのたびにフックの呼び出し順序が変わってしまい、Reactの状態管理に必要な一貫性が失われる可能性があります。

大規模なアプリケーションやチーム開発では、呼び出す関数がフックか通常の関数かをわかりやすくしておくことで、予期せぬトラブルの回避に役立ちます。

基本的な使い方

ここまでカスタムフックの基本を説明してきました。ここから簡単なコード例とともに、さらに理解を深めていきましょう。

今回は、よく使用されるuseStateをカスタムフックで1つの機能にまとめる方法を紹介します。まずは通常の組み込みフックを使って、カウントを増加させるシンプルなアプリを作成します。

// App.jsx 組み込みフックを使用した記述

import { useState } from 'react';
import Button from './components/Button';

export default function App() {
  const [count, setCount] = useState(0);
  const increment = () => {
    setCount((count) => count + 1);
  };
  const doubleIncrement = () => {
    setCount((count) => count + 2);
  };

  return (
    <>
      <Button onClick={increment} text={'+1'} />
      <Button onClick={doubleIncrement} text={'+2'} />
      <p>Count : {count}</p>
    </>
  );
}
// Button.jsx

export default function Button({ onClick, text }) {
  return <button onClick={onClick}>{text}</button>;
}

このコードで、+1ボタンと+2ボタンをクリックすると、指定の数だけカウントが増えるシンプルなカウントアップボタンが作成できました。

この記述から、useStateでのカウントアップ機能をカスタムフックとして切り離してみます。新しくuseCountUp.jsxファイルを作成し、各ファイルを以下のように編集します。

修正後のコードは以下のとおりです。

// useCountUp.jsx カスタムフックを使用した記述

import { useState } from 'react';

export default function useCountUp() {
  const [count, setCount] = useState(0);
  const increment = () => {
    setCount((count) => count + 1);
  };
  const doubleIncrement = () => {
    setCount((count) => count + 2);
  };
  
	// カスタムフックの戻り値をオブジェクトで指定
  return { count, increment, doubleIncrement };
}
// App.jsx カスタムフックを使用した記述

import useCountUp from './hooks/useCountUp';
import Button from './components/Button';

export default function App() {
  const { count, increment, doubleIncrement } = useCountUp();

  return (
    <>
      <Button onClick={increment} text={'+1'} />
      <Button onClick={doubleIncrement} text={'+2'} />
      <p>Count : {count}</p>
    </>
  );
}
// Button.jsx
// このファイルはカスタムフック作成前と変更はありません

export default function Button({ onClick, text }) {
  return <button onClick={onClick}>{text}</button>;
}

カスタムフックとしてuseCountUpを新たに作成し、App.jsxからカウントアップ機能を切り離しました。これにより、機能を独立したモジュールにまとめ、複数のコンポーネントで再利用しやすくしています。以下、変更点について確認していきましょう。

変更点

  • カウントアップ機能の切り離し

    今回はAppコンポーネントのカウントアップ機能をカスタムフックとして切り離しました。以下のAppコンポーネントの機能部分をuseCountUpコンポーネントへ切り離し、App.jsxではuseCountUpからこれらの機能を受け取るようにします。

  • 戻り値の形式

    カスタムフックの戻り値は、countincrementdoubleIncrementの3つをまとめたオブジェクトです。これにより、App.jsxでそれぞれのデータを簡単に受け取り、管理することができます。戻り値をオブジェクトにすることで、必要な値だけを柔軟に取り出せるメリットもあります。

  • シンプルになったApp.jsx

    App.jsxでは、カウントの状態管理とカウントアップ機能をuseCountUpに分離したことで、コードがよりシンプルになり、状態管理に直接関与せずに済んでいます。

カスタムフックの役割とstateについて

今回のコード例では、コンポーネント内にuseStateで作成していた機能を、カスタムフックとして1つにまとめました。これにより、他のコンポーネントでも同じカスタムフックを使って同様の機能を再利用できるようになりました。

なお、カスタムフックを使っても、各コンポーネントでのカウントアップの状態はそれぞれ独自のstateを持つため、同じカスタムフックを使っていても各コンポーネントで独立したstateが保持されます。

もし複数のコンポーネントで同じstateを共有する必要がある場合は、親コンポーネントでstateを持ち、propsを通して子コンポーネントに渡すか、useContextなどでグローバルにstateを管理する方法があります。

カスタムフックの戻り値

カスタムフックの戻り値には特に決まったルールはありませんが、React Hooksの基本的な使用パターンや他の開発者が使いやすい形を意識して設計すると良いでしょう。また、オブジェクトで返すことで後から追加する機能やデータも簡単に加えられ、長期的な拡張にも対応しやすくなります。

例えば、useStateを使用したカスタムフックでは、state(状態)とそれを操作する関数を返すことが多く、順序もuseStateと同じ「state→関数」とすると直感的に使いやすくなります。こうした一貫性のある設計は、カスタムフックを他の開発者が利用する際にも分かりやすくなります。

カスタムフックの注意点

すべての重複したコードをカスタムフックにする必要はありません。特に今回のようなシンプルな機能では、カスタムフックの大きなメリットがない場合もあります。カスタムフックは、複雑なエフェクト(useEffect)や繰り返し利用するロジックがある場合に特に有効です。

たとえば、useEffectはAPI連携や他システムとの通信で使われることが多く、コードが複雑化しやすくなります。また、多くのコンポーネントで同様の機能が必要になる場面もあります。このような複雑なエフェクトや共通のロジックをカスタムフックにまとめて使い回すことで、コードがスッキリし、保守性も向上します。

カスタムフックを作ってみよう

ここまでカスタムフックの基本とその使用方法を学んできました。このセクションでは実際にハンズオン形式でコードを記述し、さらに理解を深めていきましょう。

このハンズオンではJSONPlaceholderからデータを取得するカスタムフックを作成します。

JSONPlaceholderは、開発者がAPIを使ってデータ取得や表示の練習ができる無料のサービスです。

また、ブラウザ上で直接コードを試してみたい場合は、以下のサイトがおすすめです。フロントエンドの環境構築が不要で、すぐにコードを記述し実行できます。

カスタムフックを利用したデータ取得機能の作成

このハンズオンでは、以下のファイルを作成し編集していきます。

  • App.jsx
  • useFetchData.jsx
  • UserList.jsx
  • UserPosts.jsx

ファイルディレクトリは以下の階層としています。

src
├── App.jsx
├── hooks
│   └── useFetchData.jsx
└── components
    ├── UserList.jsx
    └── UserPosts.jsx

以下のようにコードを編集してみましょう。

// App.jsx

import React from 'react';

import './App.css';
import UserList from './components/UserList';
import UserPosts from './components/UserPosts';

export default function App() {
  return (
    <>
      <UserList />
      <UserPosts />
    </>
  );
}
// useFetchData.jsx

import { useState, useEffect } from 'react';

function useFetchData(url) {
  const [data, setData] = useState(null);        // 取得したデータを保存するstate
  const [loading, setLoading] = useState(true);   // データ取得中かどうかを示すstate
  const [error, setError] = useState(null);       // エラーがあればエラーメッセージを保存するstate

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true); // データ取得開始時にロード中の表示をオン
      try {
        const response = await fetch(url);       // 非同期でデータを取得
        if (!response.ok) {
          throw new Error('データの取得に失敗しました'); // データ取得に失敗した場合のエラーハンドリング
        }
        const result = await response.json();     // レスポンスをJSON形式に変換
        setData(result);                          // 取得したデータをstateに保存
      } catch (error) {
        setError(error.message);                  // エラーメッセージをstateに保存
      } finally {
        setLoading(false);                        // データ取得完了後にロード中の表示をオフ
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };                // コンポーネントで使えるように状態を返す
}

export default useFetchData;
// UserList.jsx

import React from 'react';
import useFetchData from '../hooks/useFetchData';

export default function UserList() {
  const { data, loading, error } = useFetchData('https://jsonplaceholder.typicode.com/users');

  if (loading) return <p>Loading...</p>;
  if (error) return <p>{error}</p>;

  return (
    <div>
      <h2>User List</h2>
      <ul>
        {data.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}
// UserPosts.jsx

import React from 'react';
import useFetchData from '../hooks/useFetchData';

export default function UserPosts() {
  const { data, loading, error } = useFetchData(
    'https://jsonplaceholder.typicode.com/posts'
  );

  if (loading) return <p>Loading...</p>;
  if (error) return <p>{error}</p>;

  return (
    <div>
      <h2>Posts List</h2>
      <ul>
        {data.map((post) => (
          <li key={post.id}>
            <h3>{post.title}</h3>
            <p>{post.body}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

コード編集後、以下のようなUIが作成できるかと思います。UserとPostsデータを取得し、画面に表示されています。

今回のハンズオンではカスタムフックを作成して、データを取得するフックを作成しました。コードを1つずつ確認していきましょう。

カスタムフックの解説

カスタムフックuseFetchDataは、APIからデータを取得し、取得結果や状態(ロード中・エラー)の管理をまとめたものです。これにより、UserListやUserPostsのような複数のコンポーネントで同じデータ取得ロジックを簡単に再利用できるようになります。

const [data, setData] = useState(null);       // 取得したデータを保存するstate
const [loading, setLoading] = useState(true); // データ取得中かどうかを示すstate
const [error, setError] = useState(null);     // エラーがあればエラーメッセージを保存するstate

data, loading, errorの3つの状態が定義されており、それぞれ以下の役割があります。

  • data

    取得したデータを保存します。データ取得が成功した際に、その結果をこの変数に格納することで、コンポーネントがデータを参照し、表示できるようになります。

  • loading

    データ取得中に表示を切り替えるためのフラグ(データ取得中かどうかの状態を示すための変数)です。このフラグを使うことで、データ取得中の状態管理が行えます。

  • error

    データ取得が失敗した場合のエラーメッセージを保存します。エラーが発生した際に、この変数にエラーメッセージを格納することで、Reactに問題が発生していることを伝えることができます。

useEffect(() => {
    const fetchData = async () => {
      setLoading(true); // データ取得開始時にロード中の表示をオン
      try {
        const response = await fetch(url);       // 非同期でデータを取得
        if (!response.ok) {
          throw new Error('データの取得に失敗しました'); // データ取得に失敗した場合のエラーハンドリング
        }
        const result = await response.json();     // レスポンスをJSON形式に変換
        setData(result);                          // 取得したデータをstateに保存
      } catch (error) {
        setError(error.message);                  // エラーメッセージをstateに保存
      } finally {
        setLoading(false);                        // データ取得完了後にロード中の表示をオフ
      }
    };

    fetchData();
  }, [url]);

次に、useEffectフック内でのデータ取得についてです。データの取得は以下の流れで処理が行われます。

  • useEffectフック内でデータを取得

    useEffectは、コンポーネントが初めて表示されるときや、依存している値(今回はurl)が変更されたときに実行され、指定したURLからデータを取得します。

  • データ取得の開始

    データ取得が始まると、まずloadingtrueに設定されます。これにより、「Loading...」のメッセージを表示してデータ取得中であることをユーザーに伝えることができます。

    取得処理が成功した場合、レスポンスがJSON形式に変換され、結果がdataに保存されます。これにより、他のコンポーネントでも取得されたデータを簡単に参照・表示できるようになります。

  • データ取得にエラーが発生した場合

    万が一エラーが発生した場合は、catchブロックによってエラーがキャッチされ、errorにエラーメッセージがセットされます。この状態により、ユーザーに「データの取得に失敗しました」とメッセージを表示して、問題があることを通知することが可能です。

  • データ取得の完了

    データ取得が完了した(成功またはエラー発生に関わらず)タイミングでloadingfalseに設定されます。これにより、ロード中の表示を非表示に切り替え、データまたはエラーメッセージが画面に表示されるようになります。

カスタムフックの効果

カスタムフックを使うことで、データ取得のロジックがシンプルになり、どちらのコンポーネントでも同じようにデータ取得が可能です。UserListとUserPostsでは、URLを変更するだけで異なるAPIデータを取得でき、データ取得後の処理もシンプルに書けるようになっています。

// UserList

export default function UserList() {
  const { data, loading, error } = useFetchData(
  'https://jsonplaceholder.typicode.com/users' // URLが異なる
  );

  if (loading) return <p>Loading...</p>;
  if (error) return <p>{error}</p>;
// UserPosts.jsx

export default function UserPosts() {
  const { data, loading, error } = useFetchData(
    'https://jsonplaceholder.typicode.com/posts' // URLが異なる
  );

  if (loading) return <p>Loading...</p>;
  if (error) return <p>{error}</p>;

このように、useFetchDataというカスタムフックを使うことで、複数のコンポーネントでデータ取得の処理をシンプルに再利用できるのがポイントです。

この記事で学んだこと

この記事を通して、カスタムフックについて学びました。最後に大切なポイントを振り返りましょう。

Reactのカスタムフックは、Reactに搭載されている標準フックの機能を組み合わせて、オリジナルの処理を持つフックを作成できる仕組みです。共通する機能を1つにまとめたり、新たな独自の機能を追加することができ、複数のコンポーネントで再利用しやすくなります。

カスタムフックに機能をまとめることで、煩雑になりがちなコンポーネントのViewとロジックを分けられます。これにより、コンポーネントの役割が分離でき、コードがスッキリして読みやすくなり、メンテナンス性が高まります。

カスタムフックは開発者が柔軟に作成できるフックですが、作成には2つのルールがあります。それは、「Reactのフックを含む関数であること」「フック名はuseで始める」ということです。フックを呼び出さない関数はカスタムフックである必要がなく、通常の関数として扱い、名前も通常の関数名にします。関数がフックを含むかどうかを明確にするために、フックを含む関数にはuseで始めることが推奨されます。

カスタムフックは自由に作成できるため、特に初学者の方は設計に迷うかと思います。さまざまな機能を組み合わせ、独自のフックを作成して理解を深めていくと良いかと思います。たくさんのカスタムフックを試してアウトプットする機会を増やし、フロントエンドの道を進んでいきましょう!

参考資料

以下のリンクは、この記事で説明した手順や概念に関連する参考資料です。より詳しく学びたい方は、ぜひご覧ください。

【番外編】USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話

IT未経験者必見 USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話

プログラミング塾に半年通えば、一人前になれると思っているあなた。それ、勘違いですよ。「なぜ間違いなの?」「正しい勉強法とは何なの?」ITを学び始める全ての人に知って欲しい。そう思って書きました。是非読んでみてください。

「フリーランスエンジニア」

近年やっと世間に浸透した言葉だ。ひと昔まえ、終身雇用は当たり前で、大企業に就職することは一種のステータスだった。しかし、そんな時代も終わり「優秀な人材は転職する」ことが当たり前の時代となる。フリーランスエンジニアに高価値が付く現在、ネットを見ると「未経験でも年収400万以上」などと書いてある。これに釣られて、多くの人がフリーランスになろうとITの世界に入ってきている。私もその中の1人だ。数年前、USBも知らない状態からITの世界に没入し、そこから約2年間、毎日勉学を行なった。他人の何十倍も努力した。そして、企業研修やIT塾で数多くの受講生の指導経験も得た。そこで私は、伸びるエンジニアとそうでないエンジニアをたくさん見てきた。そして、稼げるエンジニア、稼げないエンジニアを見てきた。

「成功する人とそうでない人の違いは何か?」

私が出した答えは、「量産型エンジニアか否か」である。今のエンジニア市場には、量産型エンジニアが溢れている!!ここでの量産型エンジニアの定義は以下の通りである。

比較的簡単に学習可能なWebフレームワーク(WordPress, Rails)やPython等の知識はあるが、ITの基本概念を理解していないため、単調な作業しかこなすことができないエンジニアのこと。

多くの人がフリーランスエンジニアを目指す時代に中途半端な知識や技術力でこの世界に飛び込むと返って過酷な労働条件で働くことになる。そこで、エンジニアを目指すあなたがどう学習していくべきかを私の経験を交えて書こうと思った。続きはこちらから、、、、

note記事3000いいね超えの殿堂記事 今すぐ読む

エンベーダー編集部

エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。

RareTECH 無料体験授業開催中! オンラインにて実施中! Top10%のエンジニアになる秘訣を伝授します! RareTECH講師への質疑応答可

関連記事

2020.02.25

完全未経験からエンジニアを目指す爆速勉強法

USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話

  • キャリア・学習法
  • エンジニア

2024.11.22

React初心者必見!コンポーネント・状態管理・Hooksをわかりやすく解説するまとめ記事

この記事では、エンベーダーで紹介してきたReactの3つの基本について、要点をまとめて紹介します。また、各技術に関する詳細な解説記事のリンクも掲載しています。この記事を読むことで、Reactの基本的な学習範囲の全体が分かり、各技術の要点を簡単に把握し、リンク先で詳細を学べるため、効率よく学習を進められます。そして、次に何を学ぶべきかが見えてくるでしょう。

  • React
  • フロントエンド

2024.10.28

Reactの基本、useCallbackを理解しよう

useCallbackは、関数をメモ化し、再レンダリング時に不必要な関数の再作成を避けることができる仕組みです。

  • React
  • フロントエンド

2024.03.24

JavaScriptって何?レベルの初心者がとりあえず触ってみるハンズオン

このハンズオンを通じて、プログラミング初心者やJavaScriptに苦手意識を持つ方でも、JavaScriptの使い方を基礎から簡単に学べます。特別な経験や知識は一切必要ありません。パソコンとインターネットの基本操作ができれば、準備は完了です。わからないことがあっても大丈夫。一旦はこういうものだと思って進めてみてください。

  • ハンズオン
  • JavaScript
  • フロントエンド