命名と責務が一致しないコンポーネントの再設計指針
はじめに
React 開発では 「名前は軽快でも中身は巨大」 ― そんなコンポーネントに出会うことが珍しくありません。
レビューアーは 命名と実装責務の乖離 を早期に検出しなければ、後続開発者の認知負荷を増大させます。本稿では、命名と責務がずれている兆候 を整理し、再設計の観点と提案方法をまとめます。
検索キーワード:React コンポーネント 責務分離
, 命名 規約
, ファットコンポーネント リファクタリング
なぜ命名と責務がずれるのか
- 初期スコープの肥大化
MVP フェーズで仮配置したロジックが段階的に膨張し、命名更新が置き去りになる。 - 業務ドメインの曖昧さ
ビジネス用語と UI 用語が混在し、適切な抽象度が見えなくなる。 - レビュー観点の不足
「ファイルサイズ」「行数」の定量的指標だけを注視し、名前と内部構造の意味的整合 を検査しない。
用語解説:ファットコンポーネントとは
「単一責任の原則」を満たさず、多数の状態・副作用・イベント処理を抱えた巨大なコンポーネントの俗称です。可読性・再利用性・テスト容易性が損なわれる傾向にあります。
兆候チェックリスト
- ファイル名と export 名が UI 意図を示さない
例:Dashboard.jsx
が内部でモーダルや REST 呼び出しまで担う。 useEffect
が複数の無関連副作用を処理- Props が10項目を超え、プレーンオブジェクトでバケツリレー
- ハンドラ名が
handleClick*
系で乱立し、ユースケースが読めない - 他レイヤー(API / Router / i18n)への直接依存が3種以上
レビュー観点 1:命名の粒度と責務の照合
ダッシュボードコンポーネント(初版)
export function Dashboard() {
const [users, setUsers] = useState<User[]>([]);
const [open, setOpen] = useState(false);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(setUsers);
@ReviewerREST 呼び出しは別 Hook へ抽出し、UI から切り離す方が読みやすいです }, []);
const handleSubmit = (payload: NewUser) => {
fetch('/api/users', { method: 'POST', body: JSON.stringify(payload) })
.then(() => setOpen(false));
@Reviewer「Dashboard」という名前から POST 処理は連想しづらいです。責務が混在しています };
return (/* 略 */);
}
- 診断
- コンポーネント名から ユーザ追加ロジック を想像しづらい
useEffect
とhandleSubmit
がネットワーク境界を直接操作- 命名を変えるべきか、責務を分割すべきか両面検討
レビュー観点 2:命名再考 or 責務分離の判断基準
- UI レイヤーが唯一の責務 → 名前を UI コンポーネント視点に統一
- 副作用・データ取得・状態計算を同居 → 専用 Hook / Container で分離
- 機能単位のまとまりが 300 行超 → ファイル物理分割 + 名前の再定義
再設計パターン
パターンA:Custom Hook へ副作用を移譲
副作用切り出し例
function useUsers() {
const [users, setUsers] = useState<User[]>([]);
const fetchUsers = async () => { /* 略 */ };
useEffect(() => { fetchUsers(); }, []);
return { users, fetchUsers };
}
export function UserDashboard() {
const { users, fetchUsers } = useUsers();
// UI 描画に専念
}
- 効果 : 命名が
UserDashboard
で UI 意図に集中 - 残課題 : POST 処理が UI 内に残るかどうか
パターンB:Container / Presentational 分割
分割構成
src/
├ containers/
│ └ UserDashboardContainer.tsx
└ components/
└ UserDashboard.tsx
- コンテナは
useUsers
とuseCreateUser
を合成 - 純粋 UI は props 経由で描画し、副作用を持たない
パターンC:ディレクトリごとの Bounded Context
業務ドメインが複合の場合、ディレクトリ単位で責務境界 を設ける。
src/ └ user/ ├ ui/ │ └ Dashboard.tsx ├ hooks/ │ ├ useUsers.ts │ └ useCreateUser.ts └ model/ └ user.ts
UMLで可視化する依存関係
ケーススタディ:命名と責務の乖離をレビューでどう指摘するか
リアルなPR抜粋
export function Analytics() {
const [activeTab, setActiveTab] = useState<'daily' | 'monthly'>('daily');
// REST, chart.js, i18n… 500行超
@Reviewer- コンポーネント名に対して内部責務が多岐にわたります- データ取得・チャート描画・翻訳切替は別層へ分割しUI はタブ切替とプレゼンテーションだけに集中させましょう@Developer分割後の命名は `AnalyticsContainer` / `AnalyticsChart` で考えていますご意見ください}
- レビューポイント
- 名前再考:
Analytics
が広義すぎる場合は限定語を付す - 責務抽出:
ChartRenderer
,useAnalyticsData
,TabState
等へ分割 - テスト戦略:分割により単体テストが可能になりカバレッジ向上
- 名前再考:
エビデンスと参考資料
- React公式ブログ「A Complete Guide to Component Design」(2024-11-12 公開)
- GitHub Octoverse 2024 Report 「Top React Antipatterns」
- Nielsen Norman Group UX Research 「Component Naming and Cognitive Load」(調査番号 NN-UX-22-C013)
まとめ
命名と責務の乖離は コード迷子 を招き、保守コストを跳ね上げます。
レビューアーは「名前→責務→依存」の三段階で照合し、分割と命名の両輪 で再設計を提案することが望ましいです。まずは Custom Hook 抽出や Container 分離といった 低コストなリファクタ から着手し、コンポーネントの意図を名前に正しく映し出す構造を目指しましょう。