【実務・中級編】 Custom Hooksの設計原則と命名規則 – React実践ガイド

ReactのCustom Hooksを「なんとなく」で作るな。設計の極意と依存関係の制御術

フロントエンドの現場で、コンポーネントが肥大化して「読み解くのに3分かかる」コードに出会ったことはないだろうか?

ReactにおけるCustom Hooksは、単なる「コードのコピペ回避」の道具ではない。それは「UIとロジックを分離し、メンタルモデルを整理するための強力なアーキテクチャ」だ。今日は、中級者から一歩先へ進むために、現場で生き残るためのCustom Hooks設計の鉄則を叩き込む。

—

1. なぜ `use` で始めるのか? 脳内コンパイラの視点

まず基本中の基本だが、`use` という接頭辞には深い理由がある。これは単なる規約ではない。Reactの内部処理において、`use` がついている関数は「Hooksを呼び出せる特権階級」として扱われるからだ。

もしルールを破ると、`eslint-plugin-react-hooks` に怒られるだけでなく、Reactの内部的な実行コンテキスト(Fiberノードに紐づくHooksリスト)と整合性が取れなくなり、`Render phase` で予期せぬバグを招く。ブラウザはコンポーネントが再レンダリングされるたびに、このリストを上から順に辿って状態を復元している。ここに条件分岐でHooksを置くような真似をすれば、Reactは「あれ?前回と順番が違うぞ?」とパニックを起こす。

だからこそ、Hooksは「コンポーネントのトップレベル」で「条件に左右されず」呼ぶ。これが鉄則だ。

—

2. 現場で使える「副作用分離」のサンプルコード

例えば、データの取得とローディング状態、エラーハンドリングを抱えたコンポーネントは、すぐにスパゲッティ化する。これをCustom Hooksで抽象化してみよう。

import { useState, useEffect, useCallback } from ‘react’;

/

  • 実践的なFetch Hooksの設計
  • 責務:データの取得、Loading/Error管理に特化する

/
export function useAsyncFetch(fetcher: () => Promise) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

const execute = useCallback(async () => {
setLoading(true);
setError(null);
try {
const result = await fetcher();
setData(result);
} catch (e) {
setError(e instanceof Error ? e : new Error(‘Unknown error’));
} finally {
setLoading(false);
}
}, [fetcher]); // fetcher自体の参照が変わるなら、ここで制御する

return { data, loading, error, execute };
}

このコードのポイントは、「状態更新ロジックをコンポーネントの関心事から切り離した」ことにある。使う側は、「どうやって取得するか」を気にせず、「結果がどうなったか」だけを宣言的に記述できる。

—

3. 依存関係の管理:沼にはまるポイント

Custom Hooksを設計する上で、最も泥臭く、かつ重要なのが `useEffect` や `useCallback` の依存配列(dependency array)の管理だ。

よくある失敗は、`useEffect` の中でオブジェクトを直接作成し、それを依存配列に入れて無限ループを誘発することだ。Reactは依存配列の中身を `Object.is` で比較している。関数やオブジェクトの参照が毎回変われば、`useEffect` は「お、新しい仕事だな」と勘違いして永遠にループし続ける。

解決策:参照の安定化

もしHooksにオブジェクトを渡す必要があるなら、`useMemo` でラップするか、定数として外に出すのが鉄則だ。

// コンポーネント側での呼び出し例
const fetchOptions = useMemo(() => ({ limit: 10 }), []); // 参照を固定する

const { data, loading } = useAsyncFetch(() => api.getItems(fetchOptions));

—

4. チーム開発における「命名と分割」の極意

Custom Hooksは、「何をするか(What)」ではなく「何を得るか(Return)」を基準に命名すべきだ。

  • 悪い例: `useGetUserData()`(関数名っぽい)
  • 良い例: `useUser()`(そのHooksが何を提供するかを表す)

また、コンポーネントの分割設計においては、「ロジックが20行を超えたらHooksへ追い出す」くらいの感覚でいい。ただし、「そのHooksを複数のコンポーネントで使う見込みがあるか?」は常に自問自答すること。1箇所でしか使わないロジックを無理に抽象化すると、コードを追うためのジャンプ回数が増え、かえって可読性を下げることもある。

—

最後に:プロの心得

Custom Hooksの最大の目的は、コンポーネントを「見た目の記述に集中させること」だ。

ロジックがHooksに移譲され、コンポーネントが純粋なUIの表現(JSX)だけになれば、テストも書きやすくなるし、修正時に「どこを直せばいいか」の迷いも消える。

「便利そうだから」でHooksを作らず、「このロジックはUIから独立させてテスト可能にしたい」という意思を持って書いてほしい。それが、明日から君のコードを一段上の品質へと引き上げるための唯一の道だ。

さあ、エディタを開いて、その肥大化したコンポーネントにメスを入れてみよう。応援している。

コメント

タイトルとURLをコピーしました