概要
「再利用できるコンポーネント」とは、使い回し可能なUI部品という意味だけではない。
それは**“責務が単一で、入力(Props)・状態(State)・副作用(Effects)が明確に分離された構造”**である。
JavaScript(およびReact/VueなどのUIライブラリ)において、コンポーネントが肥大化する原因は、
処理の責任と関心ごとが混在していることにある。
本稿では、引数・状態・副作用の構造的分離による、拡張可能でテスタブルな再利用コンポーネント設計戦略を解説する。
1. 責務の単一性(SRP)を軸に設計
// ❌ 責務が混在:描画・ロジック・副作用
function UserCard({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/user/${userId}`).then(res => res.json()).then(setUser);
}, [userId]);
return <div>{user?.name}</div>;
}
// ✅ 分離:データ取得と表示の責務を明確に
function useUser(userId) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/user/${userId}`).then(res => res.json()).then(setUser);
}, [userId]);
return user;
}
function UserCard({ userId }) {
const user = useUser(userId);
return <div>{user?.name}</div>;
}
- ✅ ロジックの分離により、テストや差し替えが可能
- ✅ 表示コンポーネントは純粋関数的に記述できる
2. 入力(Props)設計の明確化
function Button({ label, onClick, type = 'primary' }) {
return <button className={`btn btn-${type}`} onClick={onClick}>{label}</button>;
}
- ✅ 引数は明示的に列挙し、オブジェクトごとに渡さない
- ✅
label
,onClick
,type
などの責務は明確で単純
3. 状態(State)の封じ込め戦略
- ✅ 「内部状態」と「外部から渡す状態」は明確に分離する
- ✅ 入力が全てで決まるものは stateless(純粋)コンポーネント に
function Counter({ count, onIncrement }) {
return (
<button onClick={onIncrement}>
Count: {count}
</button>
);
}
function CounterContainer() {
const [count, setCount] = useState(0);
return <Counter count={count} onIncrement={() => setCount(count + 1)} />;
}
- ✅ 再利用性を上げるには、stateful → statelessへの分離が基本戦略
4. 副作用の構造化と限定化
useEffect(() => {
document.title = `Page ${page}`;
}, [page]);
- ✅ 副作用は
useEffect
や外部API呼び出しとして明示的に分離 - ✅ 表示・ロジック・副作用を物理的にファイル分離することも検討
設計判断フロー
① このコンポーネントは表示だけか? → statelessに切り出し可
② 副作用はどこで起きているか? → useEffect に限定し、他から切り離す
③ 状態は本当に必要か? → propsで注入可能なものは外部化
④ テスト可能か? → 表示とロジックを分離して、Mockが差し替えられる構造か
⑤ コンポーネントの役割は1文で説明できるか? → 単一責務に絞る
よくあるミスと対策
❌ 状態を持つコンポーネントが表示とAPI処理とイベント処理すべて抱える
→ ✅ 状態・副作用・ロジックをフック化 or containerに分離
❌ 小さすぎるコンポーネント分割でprops地獄に
→ ✅ 責務と再利用性を基準に分割深度を調整
❌ propsとしてオブジェクトや関数をそのまま渡して再レンダリング地獄
→ ✅ useMemo / useCallback / 分解して渡すことで安定性確保
結語
再利用可能なコンポーネント設計とは、「どこでも使える汎用UI部品」ではない。
それは**“引数・状態・副作用の境界を設計として分離し、拡張性と保守性を担保する構造戦略”**である。
- 表示は表示、ロジックはロジック、副作用は副作用で切り分ける
- 再利用性とは「どこでも使える」ではなく、「責務が明確で読みやすい」こと
- 入力と出力を制御可能な設計が、保守性・テスタビリティを大きく高める
JavaScriptにおける再利用可能なコンポーネント設計とは、
“責務の分離を通じて、再利用・差し替え・検証可能性を最大化するための構造設計である。”