mcpField
R
react-patterns

react-patterns

github.com/jezweb開発1.1k1.3k 回インストール3 週間前に更新
リポジトリ

react-patterns について

React 19、Vite、Cloudflare Workers のプロジェクト向けに、コンポーネント作成、コードレビュー、リファクタリングで使うパターンガイドです。性能、コンポジション、データ取得、環境固有の注意点を優先度順に扱います。

主な機能

  1. 01優先度順の性能ルール
  2. 02非同期リクエストのウォーターフォール削減
  3. 03バンドルサイズと再レンダーの管理
  4. 04コンポーネント構成と設計の指針
  5. 05React 19 固有のパターン
  6. 06Vite と Cloudflare の注意点

活用シーン

  1. 01React コンポーネントの作成やレビュー
  2. 02遅延や再レンダーの原因調査
  3. 03プロパティが多いコンポーネントのリファクタリング
  4. 04マージ前の変更確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jezweb/claude-skills --skill react-patterns

インストール後、Claude Code で/react-patternsを入力して読み込まれたか確認します。

詳細

React パターン

React 19 + Vite + Cloudflare Workers プロジェクト向けのパフォーマンスとコンポジションのパターンです。新しいコンポーネントを書く際のチェックリスト、既存コードを監査する際のレビューガイド、または遅さや複雑さを感じたときのリファクタリング手順として活用してください。

ルールは影響度の高い順に並んでいます。MEDIUM の問題に取り組む前に、CRITICAL の問題を修正してください。

適用するタイミング

  • React コンポーネントやページを新しく作成するとき
  • パフォーマンスの問題がないかコードをレビューするとき
  • props が多すぎる、または再レンダーが多いコンポーネントをリファクタリングするとき
  • 「なぜ遅いのか?」「なぜ再レンダーされるのか?」をデバッグするとき
  • 再利用可能なコンポーネントライブラリを構築するとき
  • マージ前にコードレビューするとき

1. ウォーターフォールの解消(CRITICAL)

並列で実行できる処理を逐次実行する非同期呼び出しです。パフォーマンスを損なう最大の原因です。

パターン問題解決策
順次 awaitconst a = await getA(); const b = await getB();const [a, b] = await Promise.all([getA(), getB()]);
子コンポーネントでのフェッチ親がレンダーされた後に子がフェッチし、さらにその後で孫がフェッチするフェッチを最も近い共通の祖先に引き上げ、データを子へ渡す
Suspense の連鎖複数の Suspense 境界が順番に解決される非同期の兄弟コンポーネントすべてを 1 つの Suspense 境界で囲む
分岐前の awaitconst data = await fetch(); if (condition) { use(data); }await を分岐の中に移動する。使わない可能性のあるデータはフェッチしない
インポート後にレンダーconst Component = await import('./Heavy'); return <Component />React.lazy() + <Suspense> を使う。フォールバックを即座にレンダーする

見つけ方: コンポーネント内で await を検索してください。それぞれの await はウォーターフォールの可能性があります。2 つの await が独立している場合は、並列で実行してください。

2. バンドルサイズ(CRITICAL)

ユーザーがダウンロードする 1 KB ごとに、待ち時間が発生します。

パターン問題解決策
バレルインポートimport { Button } from '@/components' はバレルファイル全体を読み込むimport { Button } from '@/components/ui/button' — 直接インポートする
コード分割なし重いコンポーネントがすべてのページで読み込まれるReact.lazy(() => import('./HeavyComponent')) + <Suspense>
起動時のサードパーティライブラリアプリのレンダー前に分析やトラッキングが読み込まれるハイドレーション後に読み込む: useEffect(() => { import('./analytics') }, [])
ライブラリ全体のインポートimport _ from 'lodash'(70KB)import debounce from 'lodash/debounce'(1KB)
Lucide のツリーシェイキングimport * as Icons from 'lucide-react'(すべてのアイコン)明示的な指定: import { Home, Settings } from 'lucide-react'
React の重複ライブラリが独自の React をバンドルしている → 「Cannot read properties of null」vite.config.ts に resolve.dedupe: ['react', 'react-dom'] を設定する

見つけ方: npx vite-bundle-visualizer — バンドルに含まれる内容を確認できます。

3. コンポジションアーキテクチャ(HIGH)

コンポーネントの構成方法は、最適化の方法よりも重要です。

パターン問題解決策
boolean props の増加<Card isCompact isClickable showBorder hasIcon isLoading>明示的なバリアントを使う: <CompactCard>、<ClickableCard>
複合コンポーネント15 個の props を持つ複雑なコンポーネント<Dialog>、<Dialog.Trigger>、<Dialog.Content> に分割し、コンテキストを共有する
renderX props<Layout renderSidebar={...} renderHeader={...} renderFooter={...}>children と名前付きスロットを使う: <Layout><Sidebar /><Header /></Layout>
状態のリフトアップ兄弟コンポーネント間で状態を共有できない状態を親またはコンテキストプロバイダーに移動する
プロバイダーの実装コンシューマーのコードが状態管理の内部実装を知っているプロバイダーはインターフェース { state, actions, meta } を公開し、実装は隠す
インラインコンポーネントfunction Parent() { function Child() { ... } return <Child /> }Child を Parent の外で定義する。インラインコンポーネントはレンダーのたびに再マウントされる

判断基準: コンポーネントに boolean props が 5 個を超えてある場合は、props を増やすのではなくコンポジションが必要です。

4. 再レンダリングの防止(中)

再レンダリングがすべて悪いわけではありません。目に見えるカクつきや無駄な計算を引き起こす再レンダリングだけを修正してください。

パターン問題解決策
デフォルトのオブジェクト/配列の propsfunction Foo({ items = [] }) → レンダリングのたびに新しい配列参照が作られます外部に定義します:const DEFAULT = []; function Foo({ items = DEFAULT })
effect 内の派生 stateuseEffect(() => setFiltered(items.filter(...)), [items])レンダリング中に派生させます:const filtered = useMemo(() => items.filter(...), [items])
オブジェクトの依存関係config が {} の場合、useEffect(() => {...}, [config]) はレンダリングのたびに実行されますプリミティブ値を依存関係に使います:useEffect(() => {...}, [config.id, config.type])
使用しない state の購読コンポーネントは { user, theme, settings } を読み取りますが、使用するのは user だけですコンテキストを分割するか、セレクターを使います:useSyncExternalStore
一時的な値に state を使うマウス移動時に const [mouseX, setMouseX] = useState(0) を使います頻繁に変化するものの、再レンダリングが不要な値には useRef を使います
インラインのコールバック props<Button onClick={() => doThing(id)} /> — レンダリングのたびに新しい関数が作られますuseCallback または関数型の setState を使います:<Button onClick={handleClick} />

見つけ方:React DevTools Profiler の「Why did this render?」を確認するか、開発環境で<React.StrictMode>による二重レンダリングを確認します。

5. React 19 固有の項目(中)

React 19 で変更された、または新しく追加されたパターンです。

パターン旧(React 18)新(React 19)
フォームの stateuseFormStateuseActionState — 名前が変更されました
ref の転送forwardRef((props, ref) => ...)function Component({ ref, ...props }) — ref は通常の prop です
コンテキストuseContext(MyContext)use(MyContext) — 条件分岐やループ内でも使えます
保留中の UI手動でローディング state を管理緊急性の低い更新にはuseTransitionとstartTransitionを使います
ルートレベルの遅延読み込みcreateBrowserRouterでのみ動作しますこれは変わりません — <BrowserRouter>では<Route lazy={...}>は何も通知されずに無視されます
楽観的更新手動で state を管理useOptimisticフック
メタデータHelmet または手動で<head>を管理コンポーネントの JSX に<title>、<meta>、<link>を記述すると、自動的に<head>へ移動されます

6. レンダリングのパフォーマンス(中)

パターン問題解決策
読み込み時のレイアウトシフト非同期データの到着時にコンテンツが跳ねます最終的なレイアウト寸法に合わせたスケルトン画面
SVG を直接アニメーションするSVG アニメーションがカクつきます<div>で囲み、代わりに div をアニメーションさせます
大きなリストのレンダリングテーブルやリストに 1000 件以上の項目があります仮想化レンダリングには@tanstack/react-virtualを使います
content-visibilityスクロール可能な長いコンテンツが最初にすべてレンダリングされます画面外のセクションにcontent-visibility: autoを指定します
&& による条件付きレンダリング{count && <Items />}は count が 0 の場合に0をレンダリングします三項演算子を使います:{count > 0 ? <Items /> : null}

7. データ取得(中)

パターン問題解決策
重複排除がない3 つのコンポーネントが同じデータを取得しますTanStack Query または SWR — 自動的に重複排除とキャッシュを行います
マウント時に取得するuseEffect(() => { fetch(...) }, []) — ウォーターフォールが発生し、キャッシュも重複排除もありませんTanStack Query を使います:useQuery({ queryKey: ['users'], queryFn: fetchUsers })
楽観的更新がないユーザーが保存をクリックしてから、変更が表示されるまで 2 秒待ちますonMutateを指定したuseMutationで、視覚的なフィードバックを即座に表示します
インターバル内の古いクロージャsetIntervalが古い state を捕捉しますインターバル ID と現在の値にはuseRefを使います
クリーンアップなしのポーリングuseEffect 内のsetIntervalにclearIntervalがありませんクリーンアップ関数を返します:useEffect(() => { const id = setInterval(...); return () => clearInterval(id); })

8. Vite + Cloudflare 固有の項目(中)

パターン問題修正方法
Node スクリプト内の import.meta.env未定義です。Vite で処理されたファイル内でのみ動作しますvite の loadEnv() を使用します
React の重複インスタンスライブラリが独自の React をバンドルしていますvite.config.ts で resolve.dedupe + optimizeDeps.include を設定します
Radix Select の空文字列<SelectItem value=""> はエラーになりますセンチネル値を使用します:<SelectItem value="__any__">
React Hook Form の null{...field} は Input に null を渡します手動でスプレッドします:value={field.value ?? ''}
エッジでの環境変数Workers では process.env が存在しませんc.env(Hono コンテキスト)または import.meta.env(Vite のビルド時)を使用します

レビューチェックリストとして使用する

コードをレビューする際は、すべての PR でカテゴリ 1〜3(重大 + 高)を確認します。パフォーマンスが懸念される場合にのみ、カテゴリ 4〜8 を確認します。

/react-patterns [file or component path]

ファイルを読み、優先度順にルールと照らし合わせて確認し、次の形式で指摘事項を報告します:

file:line — [rule] description of issue

コメント