Reactでカスタムフックを複数組み合わせて使う方法を初心者向けに解説
生徒
「先生、Reactのカスタムフックって複数組み合わせて使うことはできますか?」
先生
「もちろんできます。カスタムフックは他のフックと同じように関数ですから、組み合わせて使うことも可能です。」
生徒
「でも、組み合わせるとコードが複雑になりませんか?」
先生
「ポイントを押さえれば大丈夫です。再利用しやすく整理されたカスタムフック同士を組み合わせることで、コードがむしろ読みやすくなります。」
1. 複数のカスタムフックを組み合わせるメリット
Reactでカスタムフックを複数組み合わせることで、次のようなメリットがあります。
- 再利用性の向上:カウント機能やデータ取得機能など、単機能のフックを組み合わせることで複数のコンポーネントで共通して使えます。
- コードの整理:処理を小さな単位に分けて管理できるため、コンポーネント側のコードがシンプルになります。
- メンテナンスが容易:問題が発生したときに、個別のフックだけを修正すればよく、影響範囲を最小限にできます。
2. 複数フックの組み合わせ例
例えば、カウントとデータ取得の2つのカスタムフックを組み合わせて使う場合を考えてみましょう。
import { useState, useEffect } from "react";
export function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return { count, increment, decrement };
}
export function useFetchData(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
}
fetchData();
}, [url]);
return { data, loading };
}
この2つのフックはそれぞれ独立していますが、1つのコンポーネント内で同時に使うことができます。
import React from "react";
import { useCounter, useFetchData } from "./hooks";
function App() {
const { count, increment, decrement } = useCounter(5);
const { data, loading } = useFetchData("https://jsonplaceholder.typicode.com/todos/1");
return (
<div>
<h2>カウント: {count}</h2>
<button onClick={increment}>増やす</button>
<button onClick={decrement}>減らす</button>
<h2>データ取得結果:</h2>
{loading ? <p>読み込み中...</p> : <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
);
}
export default App;
3. 複数フックを組み合わせる際のポイント
複数フックを組み合わせるときは、以下の点に注意すると使いやすくなります。
- 単一責任のフックを作る:1つのフックは1つの役割だけに絞ると、組み合わせやすくなります。
- 依存関係を意識する:フック同士で状態を共有する場合は、順序や依存関係を明確にしましょう。
- エラー処理やローディング管理をフック内に閉じる:コンポーネントは呼び出すだけにすると、複雑さが減ります。
- 引数で柔軟に対応する:初期値やURLなど、フックが外部から受け取れるようにしておくと汎用性が上がります。
4. 複数フック活用のまとめポイント
カスタムフックを複数組み合わせることで、Reactアプリのコードはより整理され、再利用性が高まります。単一責任の原則を守りつつ、必要に応じて引数や状態を管理することで、複雑な処理も簡単に扱えるようになります。
まとめ
Reactの開発において、カスタムフックを効果的に組み合わせることは、単にコードを短くするだけでなく、アプリケーション全体のアーキテクチャを堅牢にするために非常に重要な技術です。これまでに学んだ通り、カスタムフックは「ロジックの再利用」を目的とした強力なツールですが、それらを複数使いこなすことで、複雑なUIの状態管理や外部APIとの通信、イベントリスナーの制御などを驚くほどスッキリと記述できるようになります。
初心者のうちは、一つの大きなコンポーネントの中にすべてのロジックを詰め込みがちですが、それはメンテナンス性を低下させる原因となります。今回紹介した「単一責任の原則」に基づき、例えば「入力フォームの管理」「データのフェッチ」「ウィンドウサイズの監視」といった役割ごとにフックを分割し、それらを必要に応じてインポートして利用するスタイルを身につけましょう。
さらに応用的な活用方法として、あるカスタムフックの戻り値を別のカスタムフックの引数に渡す「フックの連鎖」も可能です。これにより、データに基づいた動的なロジックの構築が可能になります。ただし、その際にはReactの「フックのルール」を厳守し、条件分岐の中でフックを呼び出さない、常にトップレベルで呼び出すといった基本を忘れないようにしてください。
実践的な複数カスタムフックの構築例
ここでは、より実践的な例として、入力フォームの状態管理(useInput)と、その入力内容をローカルストレージに保存するロジック(useLocalStorage)を組み合わせてみましょう。これにより、ページをリロードしても入力内容が消えない便利なフォームが簡単に作れます。
import { useState, useEffect } from "react";
// ローカルストレージとの同期を行うカスタムフック
export function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = (value) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// フォームの入力を管理するカスタムフック
export function useInput(initialValue) {
const [value, setValue] = useState(initialValue);
const handleChange = (e) => {
setValue(e.target.value);
};
return { value, onChange: handleChange, setValue };
}
次に、これらのフックを組み合わせて一つのコンポーネント内で使用するコードです。
import React, { useEffect } from "react";
import { useInput, useLocalStorage } from "./myHooks";
function UserProfileEditor() {
// 1. ローカルストレージの状態を管理するフック
const [savedName, setSavedName] = useLocalStorage("user-name", "");
// 2. フォーム入力を管理するフック
const nameInput = useInput(savedName);
// 保存ボタンを押した時の処理
const handleSave = () => {
setSavedName(nameInput.value);
alert("名前を保存しました!");
};
return (
<div className="p-4 border rounded shadow-sm">
<h3 className="mb-3">プロフィールの編集</h3>
<div className="mb-3">
<label className="form-label">お名前:</label>
<input
type="text"
className="form-control"
value={nameInput.value}
onChange={nameInput.onChange}
/>
</div>
<p>現在の入力内容:{nameInput.value}</p>
<button className="btn btn-primary" onClick={handleSave}>
ローカルストレージに保存
</button>
<div className="mt-3 text-muted small">
※保存すると、ブラウザを閉じても名前が保持されます。
</div>
</div>
);
}
export default UserProfileEditor;
このように、フックを分けることで「データの保存に関するロジック」と「UIの入力値に関するロジック」が分離され、それぞれのフックを他のコンポーネントでも使い回すことができるようになります。これがReactにおけるクリーンなコードの書き方の第一歩です。
生徒
「先生、まとめのサンプルコードを見て感動しました!useLocalStorageとuseInputを組み合わせるだけで、こんなに高機能なフォームが作れるんですね。」
先生
「そうでしょう。もしこれを一つのコンポーネントの中に全部書いていたら、useEffectやtry-catchの処理でコードが埋め尽くされて、何をしているのか分からなくなってしまいますからね。」
生徒
「確かに、コンポーネントの中身は『フックを呼び出すだけ』になっているので、すごく読みやすいです。でも、フックをたくさん使いすぎると重くなったりしませんか?」
先生
「良い質問ですね。基本的に、フックを複数使うこと自体でパフォーマンスが極端に落ちることはありません。大切なのは『いつ再レンダリングが起きるか』を意識することです。必要であればuseMemoやuseCallbackを使って最適化を組み合わせるのも一つの手ですよ。」
生徒
「なるほど。まずは機能をしっかり分割して、読みやすいコードを書くことから始めてみます。他にも便利なカスタムフックを作ってみたくなりました!」
先生
「その意気です。例えば、ネットワークの接続状況を確認するフックや、スクロール位置を追跡するフックなど、アイデア次第でどんどん開発が楽になります。今回学んだ『複数フックの組み合わせ』を活用して、ぜひオリジナルの機能を実装してみてくださいね。」
生徒
「はい!自分だけのカスタムフックライブラリを作ってみます。先生、ありがとうございました!」