カスタムHookの命名と構造から読み解く設計意図

Reactプロジェクトのコードレビューにおいて、カスタムHookはしばしば「設計思想の痕跡」が最も表れやすい箇所の一つです。特に命名や内部構造を確認することで、責務が正しく分離されているか、構造がコンポーネント設計と整合しているかといった判断を行う材料になります。

本記事では以下の観点から、レビューアーがカスタムHook設計に対してどう読み解き、指摘すべきかを整理します。

  • カスタムHookの命名と責務の関連
  • useEffect依存と副作用抽出の適切性
  • 複数責務の分離判断と抽出単位の適合性
  • Hook再利用とスコープ汚染の検知ポイント
カスタムHookとはReactにおけるカスタムHookとは、useStateuseEffect などのビルトインHookを組み合わせ、独自のロジックや状態管理を共通化・再利用可能にするための関数群です。命名規則として useXxx の形式が求められます。

命名から読み取る「責務の範囲」

カスタムHookの命名は、責務を把握する最初の手がかりです。命名が抽象的・汎用的であるほど、「本来のユースケースが見えづらい」という問題があります。以下は実際に遭遇しやすい構造です。

抽象化しすぎたHookの例
export const useManager = () => {
  const user = useUser();
  const data = useData();
  const isAdmin = checkAdmin(user);

  useEffect(() => {
    // admin判定によりデータをフィルタリング
  }, [isAdmin]);

  return { data };
};
命名の曖昧さと責務集中の指摘
@Reviewer: useManagerという命名では具体的な責務が読み取れず、使用側も期待する挙動を把握しにくくなっています。また、useUser・useDataの取得、権限判定、表示ロジックが1つのHookに混在しており、再利用性が乏しい構造です。

命名は「データの出自」や「利用意図」に即したものとし、構造的にも1つの責務に対して1Hookを原則とする構成が望ましいです。

useEffect依存構造と副作用の抽出判断

コンポーネント外にロジックを抽出したとしても、useEffectに強く依存する構造である場合、それは「見かけだけの再利用可能化」であり、むしろ可読性・デバッグ性を損なう要因となります。

以下のようなコードは、Hook内部に副作用が密集し、責務がブラックボックス化してしまっています。

useEffect依存が強すぎる構造
export const useDashboardData = (userId: string) => {
  const [data, setData] = useState(null);

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

  return { data };
};
レビューコメント例
@Reviewer: コンポーネントから副作用を抽出している点は意図が明確ですが、ユーザーIDに基づく非同期通信とデータ状態を同時に扱う構造は、単体テストやロジック分離の妨げとなりやすいです。データ取得は別Hookとして分離を検討してもよいかもしれません。

useEffectに依存するHookは、その効果範囲や呼び出しタイミング、依存配列によって挙動が大きく変わります。そのため、抽象化の単位を誤るとテスト困難な構造になりやすいという点に留意が必要です。

Hookの肥大化と分割のタイミング

「ユースケースが似ているから」という理由で、責務の異なる処理を1つのカスタムHookにまとめてしまう構造はよく見かけます。以下はその一例です。

複数責務を含むカスタムHook
export const useSearchAndSort = () => {
  const [query, setQuery] = useState('');
  const [sortKey, setSortKey] = useState('name');

  const result = useMemo(() => {
    return sortItems(searchItems(DATASET, query), sortKey);
  }, [query, sortKey]);

  return { query, setQuery, sortKey, setSortKey, result };
};
責務分離観点の指摘
@Reviewer: 検索とソートはUI上では一連の操作であるものの、責務としては別のものであり、状態管理も異なります。それぞれを独立したHookとし、必要に応じてそれらをまとめるコンポーズHookの導入を検討することで、構造の透明性が高まります。

このように、Hookの粒度は「表示単位」や「状態単位」ではなく、責務単位で切り分けていく必要があります。