再利用されないコンポーネントの構造的特徴
はじめに
「汎用的に作ったのに、結局この画面でしか使ってないんですよね……」
これはReactに限らず、UIコンポーネントの再利用性をめぐってよく聞く現場の声です。
再利用されないコンポーネントには構造上の共通点があり、レビューアーは「抽象化の目的」や「再利用シナリオとの乖離」を判断する立場にあります。
この記事では、「再利用を意識して作ったはずなのに使われない」コンポーネントの構造的特徴に焦点を当て、レビューで着目すべき設計のズレを整理します。
コンポーネント再利用の前提条件
まず前提として、再利用されるコンポーネントには以下のような条件が成立している必要があります。
- 表示の一貫性が期待されている(UIデザインの共通化)
- 処理の一貫性が期待されている(ロジックの汎用化)
- 使い回すコストより自作するコストのほうが高いと判断される
ところが、こうした条件を満たしていてもなお実際には使われないケースが頻発します。ではなぜか。
構造レビューで見かける“再利用されない”構造の典型
1. propsが過剰に抽象化されている構造
export const ActionButton = ({ variant, size, onClick, label, disabled }) => {
// variant: 'primary' | 'secondary' | 'danger'
return (
<button className={`btn ${variant} ${size}`} onClick={onClick} disabled={disabled}>
{label}
</button>
);
}
@Reviewe再利用を目的としたprops設計だが、variantやsizeなどのパターンが実装側に依存しており、スタイルガイドの意図や制約が不明確です。デザイン制約を明文化するか、適切なvariantの列挙型設計を検討すべきです。
このようなコンポーネントは一見「再利用しやすい」ように見えますが、使う側にかなりの文脈理解を要求してしまい、再利用コストが上がってしまいます。
抽象化とは
抽象化とは具体的な構造や実装を汎用的な構造に置き換えることで、再利用性を高めたりロジックの共通化を狙う設計手法です。しかし、コンテキストを持たない抽象化はむしろ理解のコストを増やします。
コンテキストが設計に反映されていない構造
よくある失敗例として、「将来使われそうだから汎用的にしておいた」という理由で、具体的な利用ケースが不在のまま設計された構造があります。
<ActionButton
variant="primary"
size="large"
label="登録"
onClick={() => registerUser(data)}
/>
この実装では「登録ボタン」というコンテキストが明確にあるのに、コンポーネントはそれを意識していません。
明示的に登録ボタン専用のコンポーネントを設計するほうが、保守性と意図の明確さでは優れています。
export const RegisterButton = ({ onRegister }) => (
<ActionButton variant="primary" size="large" label="登録" onClick={onRegister} />
);
@Reviewer: 汎用ボタンに登録用の文脈が埋もれているため、意図が読み取りづらくなっています。構造的に分離することで、読みやすさと再利用の可視性を確保しましょう。
コンポーネントが「UIの一部」として設計されていない
再利用されない構造でしばしば見られるのが、表示箇所に強く依存したローカル構造のままexportされているケースです。
悪い例:画面にベタに埋め込まれた構造
export const UserTable = ({ users, onSelect }) => {
const isAdmin = useContext(AuthContext).role === 'admin';
const filteredUsers = users.filter(u => u.active);
return (
<table>
{filteredUsers.map(user => (
<tr key={user.id}>
<td>{user.name}</td>
{isAdmin && <td><button onClick={() => onSelect(user)}>編集</button></td>}
</tr>
))}
</table>
);
}
このコンポーネントは、「adminかどうか」「activeかどうか」など、ビューごとの判断が混在しています。
良い構造の設計観点
- フィルタリングは親が責任を持つ
- 表示責務はpropsだけで完結させる
export const UserTable = ({ users, showActions, onSelect }) => (
<table>
{users.map(user => (
<tr key={user.id}>
<td>{user.name}</td>
{showActions && <td><button onClick={() => onSelect(user)}>編集</button></td>}
</tr>
))}
</table>
);
抽象化の“タイミング”を見極める
再利用できない抽象化は、「早すぎた抽象化」であることが多いです。
実際に複数画面で使われることが確定してから切り出すほうが、結果的に構造の安定性が高まります。
再利用を目的とする抽象化は、「最低2箇所で使われてから」で構わない、というのが現場では合理的です。
予測ではなく、使用実績ベースでの抽象化をレビューで促すことが重要です。
再利用されない構造の典型パターン図
まとめ:レビュー時のチェックポイント
レビューアーとして、以下の点を構造から判断できるようになっているかを確認してください。
- 再利用先が既に存在するか、または将来的に明確に計画されているか
- propsが抽象的すぎて使う側の負担が増していないか
- 表示とロジック、状態処理などの責務がコンポーネント内部に混在していないか
- 「汎用にしたつもり」がむしろ保守性を下げていないか
これらを判断できるかどうかで、再利用可能な設計を見抜けるレビューアーとしての力が試されます。