初期化ロジックが混在したコンポーネントの責務分離
はじめに
useEffect
が3つ、初期fetchもあって、状態初期化に5行──
それらがすべてコンポーネントのトップに並んでいると、構造が読みにくくなるだけでなく、再利用やテストにも影響が出てきます。
レビューアーの視点では、単に「useEffectが多い」ではなく、初期化ロジックの混在によって責務が曖昧になっていないかを見ていく必要があります。
「初期化ロジックが混在する」とは
初期化ロジックとは
コンポーネントマウント時に一度だけ実行される処理のこと。代表例はuseEffect(() => { ... }, [])
や初期のデータfetch、Reduxのdispatchなどです。
この初期処理が複数混ざっていると、コードが読みにくくなるだけでなく、以下のような副作用も出てきます。
- 副作用の順序が曖昧になりやすい
- ローディング状態とUI制御が分離できなくなる
- 単体テストしづらい構造になる
典型例とレビュー観点
初期化処理が混在している例
export function UserDashboard() {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
useEffect(() => {
fetch('/api/user')
.then(res => res.json())
.then(setUser);
}, []);
useEffect(() => {
const saved = localStorage.getItem('theme');
if (saved) setTheme(saved);
}, []);
useEffect(() => {
document.title = 'ユーザ情報';
}, []);
@Reviewer初期化ロジックが3種類混在しており、責務が明確に分かれていません。カスタムHookへの分離や、ローカル専用ロジックの外出しを検討しましょう。}
UI表示と関係のないdocument.title
やローカルストレージ読取が、同一ファイルに積まれていることで、構造の責務が不明瞭になります。
初期化を分離するパターン
パターン1:カスタムHookに処理を逃がす
useUser.ts
export function useUser() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch('/api/user')
.then(res => res.json())
.then(setUser);
}, []);
return user;
}
UserDashboard.tsx
const user = useUser();
この構造により、UserDashboardは純粋な描画ロジックに集中できるようになります。
パターン2:ドメイン別に初期化を分割する
useTheme.ts
export function useThemeInit(setTheme: (t: string) => void) {
useEffect(() => {
const saved = localStorage.getItem('theme');
if (saved) setTheme(saved);
}, []);
}
副作用がテーマに関するものであることを明示し、ドメイン軸で初期化処理を整理します。
混在構造と分離構造を比較
後者の方が構造上の依存関係が明確で、テストしやすくなることがわかります。
構造分離のチェックリスト(レビュー観点)
コメント例
初期化混在の指摘
@Reviewer: 初期化ロジックが複数混在しており、UI描画責務と関係のない副作用も含まれています。Custom Hookへ分離することで構造の明確化とテストのしやすさが向上します。
改善案コメント例
- useEffect(() => { fetch + title変更 }, []);
+ useUser(), usePageTitle() を使い責務ごとに処理を分割しましょう
まとめ
Reactにおける初期化ロジックは見落とされがちですが、構造の読みやすさ・保守性・テスト性に直結する重要な部分です。
レビューアーとしては「初期化処理がUIコンポーネントの中に溜まっていないか」を観察し、分離と整理による責務の明確化をサポートしていくことが求められます。
次回レビュー時には、useEffect
が連続している箇所を見かけたら、その中身が本当にUI層で扱うべき内容か?という目で確認してみてください。