カスタムHook+ロジックHook+UIの3層構造が破綻していないか?
カスタムHook+ロジックHook+UIの3層構造が破綻していないか?
Reactの設計で責務分離を進めていくと、しばしば以下のような3層構造が形成されます。
- UIコンポーネント:表示責務を担う最上層
- ロジックHook(カスタムHook):状態・副作用・UI制御をまとめる中間層
- ドメインHook(ロジックの再利用層):データ取得・変換・永続化処理などを扱う基盤層
このような構造は一見、再利用性・テスト容易性・責務明確性を向上させる理想的なアーキテクチャに思えます。しかし、現実のプロジェクトでは以下のような破綻が多く見られます。
- Hookの粒度が大きくなりすぎる
- 責務の分離より責務の隠蔽が進む
- Hook間の依存方向が不明瞭・密結合になる
この記事では、レビューアーが3層Hook構造を読む際に注視すべき構造的な破綻の兆候と、分離が正しく機能しているかどうかの判断基準を整理します。
3層構造の典型構成とその利点
まずは意図された3層構造の正常な例を整理します。
useUserStore.ts(ドメインHook)
export const useUserStore = () => {
const [user, setUser] = useState<User | null>(null);
const loadUser = async () => {
const res = await fetch('/api/user');
const data = await res.json();
setUser(data);
};
return { user, loadUser };
};
useProfileLogic.ts(ロジックHook)
export const useProfileLogic = () => {
const { user, loadUser } = useUserStore();
const isAdmin = user?.role === 'admin';
return { user, isAdmin, loadUser };
};
ProfileCard.tsx(UIコンポーネント)
export const ProfileCard = () => {
const { user, isAdmin, loadUser } = useProfileLogic();
useEffect(() => {
loadUser();
}, [loadUser]);
return (
<div>
<h2>{user?.name}</h2>
{isAdmin && <p>管理者</p>}
</div>
);
};
このように構成された場合、
- UIは状態や副作用を意識せず表示に専念できる
- 状態管理は1層に閉じ、再利用性が高い
- ビジネスロジックが明示的にHook化され、テスト可能
となり、構造としては理想に近い状態です。
3層構造とは
UI → ロジック → ドメイン という3つの責務レイヤーに分離された設計を指し、Reactではコンポーネント
・ロジックHook
・状態HookやデータHook
の構成としてよく現れる。
構造破綻の例1:中間Hookの肥大化と命名の曖昧化
以下は、ロジックHookがドメインロジックとUI状態を巻き込んで肥大化している例です。
useDashboard.ts
export const useDashboard = () => {
const [searchWord, setSearchWord] = useState('');
const [logs, setLogs] = useState<LogEntry[]>([]);
const { user, loadUser } = useUserStore();
const searchLogs = async () => {
const res = await fetch(`/api/logs?q=${searchWord}`);
const data = await res.json();
setLogs(data);
};
return {
user,
isAdmin: user?.role === 'admin',
logs,
searchWord,
setSearchWord,
searchLogs,
loadUser,
};
};
@Reviewer: useDashboardは、データ取得・フィルタ条件・認可判定・状態保持がすべて集約されており、責務が曖昧です。状態ごとに責務を切り出し、構造の肥大化を防ぐ必要があります。
構造破綻の例2:Hook間の依存関係が逆転・循環
useApp.ts
export const useApp = () => {
const { sidebarOpen } = useSidebarState(); // UIの状態
const { user } = useUserStore(); // ドメイン層
// ドメイン層がUI層に依存してしまっている
return { user, sidebarOpen };
};
@Reviewer:useApp内でUI表示の状態(sidebarOpen)とドメインロジック(user)が混在しており、Hook間の責務と依存方向が崩れています。UI状態はUIに近い階層に保持し、構造の階層を守ってください。
これは3層構造が逆転してしまった例です。本来、UI層が状態Hookに依存するべきであり、ドメイン層からUI状態にアクセスするのは構造違反です。
構造正常系と破綻系の違い
Hook構造のレビュー観点チェックリスト
3層構造が健全に保たれているかを判断するため、以下の観点を確認してください。
- ロジックHookがUIとドメインを接続する責務に収まっているか?
- ドメインHookが状態管理や副作用制御だけを担っているか?
- Hook間の依存関係が下方向に限定されているか?(UI → ロジック → ドメイン)
- Hook同士の責務が重複していないか?
- 各Hookの命名が責務と一致しているか?
3層構造を導入しただけで設計が良くなるわけではありません。むしろ、Hookを分離すればするほど、構造の一貫性と責任の明示性がより重要になります。
レビューアーは、コード量の多寡ではなく、層の意図が正しく実装に表れているかという視点でHook構造を読み解くことが求められます。