react-design-patterns について
React開発者向けに、13種類のコンポーネントやアプリケーション設計パターンを解説するスキルです。選択ガイドを備え、まずCustom Hookを試し、必要に応じて構造的なパターンへ進む方針を示します。
主な機能
- 01パターン選択ガイド
- 0213種類のReact設計パターン
- 03制御・非制御コンポーネントの解説
- 04ロジックと表示の分離
- 05エラー境界とPortalの解説
活用シーン
- 01再利用可能なロジックのパターン選定
- 02TabsやModalの複合コンポーネント設計
- 03データ取得と表示の分離
- 04デザインシステムの構成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ b4r7x/ agent-skills --skill react-design-patternsインストール後、Claude Code で/react-design-patternsを入力して読み込まれたか確認します。
詳細
React のデザインパターン
概要
2025年の人気度に基づいて順位付けした13のパターンです。基本原則:まずカスタムフックから始め、構造の共有が必要な場合にのみ複合コンポーネントに移行します。
パターンの選択ガイド
| 必要なもの | パターン |
|---|---|
| UI に関する意見を含まない、再利用可能なロジック | カスタムフック |
| 制御コンポーネントと非制御コンポーネントの動作 | 制御プロップ |
| 密接に連携する UI サブコンポーネント(タブ、モーダル、アコーディオン) | 複合コンポーネント |
| スタイルを強制しないロジック | ヘッドレスコンポーネント |
| データ取得とレンダリングの分離 | コンテナー/プレゼンテーショナル |
| グローバルで安定した状態(認証、テーマ) | プロバイダー(Context) |
| データフローの逆転(子 → 親) | レンダープロップ |
| children による完全なレンダリング制御 | 関数としての children |
| 事前構築済みプロップを使った柔軟な API | プロップゲッター |
| クラッシュの分離 | エラーバウンダリー |
| DOM の親の外側へのレンダリング(モーダル、ツールチップ) | ポータル |
| デザインシステムの階層構造 | アトミックデザイン |
1. カスタムフック ⭐⭐⭐⭐⭐
状態を持つロジックを再利用可能な関数に切り出します。2025年に最も人気のあるパターンです。
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try { return JSON.parse(localStorage.getItem(key)) ?? initialValue; }
catch { return initialValue; }
});
const set = useCallback((v) => {
setValue(prev => {
const next = v instanceof Function ? v(prev) : v;
localStorage.setItem(key, JSON.stringify(next));
return next;
});
}, [key]);
return [value, set];
}
2. 制御プロップ ⭐⭐⭐⭐
コンポーネントは、制御モード(親がプロップを介して状態を所有)と非制御モード(コンポーネント自身が状態を管理)の両方に対応します。すべてのフォーム入力とコンポーネントライブラリで使われています。
// Controlled — parent manages state
<EditableTitle title={title} onChange={setTitle} />
// Uncontrolled — component manages own state, parent can reset via key
<EditableTitle key={userId} defaultTitle="Untitled" />
// Implementation supporting both modes
function EditableTitle({ title, defaultTitle = '', onChange }) {
const [internal, setInternal] = useState(defaultTitle);
const isControlled = title !== undefined;
const value = isControlled ? title : internal;
const handleChange = (e) => {
if (!isControlled) setInternal(e.target.value);
onChange?.(e.target.value);
};
return <input value={value} onChange={handleChange} />;
}
3. 複合コンポーネント ⭐⭐⭐⭐
ローカルコンテキストを介して状態を共有するサブコンポーネントの一群です。Radix UI、Headless UI、React Aria で使われています。
const TabsContext = createContext(null);
function Tabs({ children, defaultTab }) { /* owns [activeTab, setActiveTab], provides both via context */ }
Tabs.Tab = /* reads context, renders the trigger button */
Tabs.Panel = /* reads context, renders children when active */
構成:親が状態を所有し、ローカルコンテキストを介して提供します。ドットで名前空間を区切ったサブコンポーネントが状態を利用します。useTabs() ガードフックは、サブコンポーネントが <Tabs> の外でレンダリングされるとエラーをスローします。実行可能な完全版の実装は react-usecontext スキルにあります(この例の唯一の管理元です)。複合コンポーネントを作成するときに読み込んでください。
4. ヘッドレスコンポーネント ⭐⭐⭐⭐
フックはロジックのみを提供し、HTML も CSS も一切含みません。UI は完全に自由に作成できます。
function useAccordion() {
const [openIndex, setOpenIndex] = useState(null);
const toggle = useCallback((i) => setOpenIndex(prev => prev === i ? null : i), []);
const isOpen = useCallback((i) => openIndex === i, [openIndex]);
return { toggle, isOpen };
}
// Two completely different UIs can use the same hook
5. コンテナー/プレゼンテーショナル ⭐⭐⭐
コンテナー(ロジック、データ取得、ナビゲーション)とプレゼンテーショナル(プロップを介してデータを受け取り、レンダリングのみを行う)に分割します。
// Presentational — pure UI, easy to test
const UserCard = ({ user, onFollow }) => (
<div><h2>{user.name}</h2><button onClick={onFollow}>Follow</button></div>
);
// Container — logic and data
const UserCardContainer = ({ userId }) => {
const { user, follow } = useUser(userId);
if (!user) return <Skeleton />;
return <UserCard user={user} onFollow={follow} />;
};
6. プロバイダー(Context) ⭐⭐⭐⭐
グローバル/ローカル状態には、createContext と useContext を使います。詳しくは react-usecontext スキルを参照してください。
7. レンダープロップ ⭐⭐
プロップとして関数を渡し、コンポーネントがデータを渡してその関数を呼び出します。
<WindowSize render={({ width }) => (
width < 768 ? <MobileMenu /> : <DesktopMenu />
)} />
データフローを逆転させる場合(子 → 親)に便利です。2025年には、ほとんどがカスタムフックに置き換えられています。
8. 関数としての children ⭐⭐
レンダープロップの派生形で、関数を children として渡します。Formik、Downshift によって広まりました。
<Toggle>
{({ on, toggle }) => (
<button onClick={toggle}>{on ? 'ON' : 'OFF'}</button>
)}
</Toggle>
9. プロップゲッター ⭐⭐
フックが、要素に展開するための事前構築済みプロップオブジェクトを返します。React Hook Form(register)、react-table で使われています。
function useToggle() {
const [on, setOn] = useState(false);
const getTogglerProps = (overrides = {}) => ({
onClick: () => setOn(prev => !prev),
'aria-pressed': on,
...overrides,
});
return { on, getTogglerProps };
}
// Usage: <button {...getTogglerProps({ className: 'my-btn' })}>
10. エラーバウンダリー ⭐⭐
コンポーネントツリー内の JS エラーを捕捉し、フォールバック UI を表示します。クラスコンポーネントまたは react-error-boundary ライブラリが必要です。
import { ErrorBoundary } from 'react-error-boundary';
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => { /* reset state */ }}>
<Dashboard />
</ErrorBoundary>
十分に活用されていません。ほとんどのアプリでは、セクションごとに細かくエラーバウンダリーを設けるべきです。
11. ポータル ⭐⭐⭐
DOM の親ツリーの外側にレンダリングします。z-index、overflow: hidden、クリッピングの問題を解決します。
import { createPortal } from 'react-dom';
function Modal({ isOpen, children }) {
if (!isOpen) return null;
return createPortal(
<div className="modal-overlay">{children}</div>,
document.body
);
}
12. HOC(高階コンポーネント) ⭐ — レガシー
コンポーネントを受け取り、新しいコンポーネントを返す関数です。現代のコードではカスタムフックに置き換えられています。
// Legacy
const ProtectedDashboard = withAuth(Dashboard);
// Modern equivalent
function ProtectedRoute({ children }) {
const { user } = useAuth();
if (!user) return <Navigate to="/login" />;
return children;
}
13. アトミックデザイン ⭐⭐⭐
階層構造:Atoms(Button)→ Molecules(SearchBox = Input + Button)→ Organisms(Header)→ Templates(データを含まないレイアウト)→ Pages(データを含むテンプレート)。デザインシステムや大規模チームのプロジェクトに最適です。
参考資料
- 複合パターン — patterns.dev — コンテキストを使った複合コンポーネントのビジュアルガイド
- React Hooks を使った複合コンポーネント — Kent C. Dodds による代表的なチュートリアル
- React のデザインパターン — Telerik — ベストプラクティスを含む現代的なパターンの概説