react-dev について
FixMyBerlin/FMC の React・TypeScript コンポーネント規約をまとめたスキルで、型付けと `useEffect` の適切な使用を中心に扱います。React コードを書く開発者やレビュー担当者向けで、ルーティングやサーバー処理は関連スキルを案内します。
主な機能
- 01React 19 のコンポーネントと ref の型付け
- 02名前付き `useEffect` 関数
- 03Effect を避ける場面の指針
- 04React Compiler と Oxlint の規約
- 05React 19 API の利用メモ
活用シーン
- 01コンポーネントの props とイベント処理の型付け
- 02Effect のロジックの作成やレビュー
- 03派生状態やデータ取得での Effect 回避
- 04React Compiler と lint の規約適用
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ fixmyberlin/ fixmyskills --skill react-devインストール後、Claude Code で/react-devを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
examples/generic-components.mdreferences/effects.mdevent-handlers.mdreact-19-patterns.mdtanstack-router.md詳細
LLM リファレンス: react.dev/llms.txt · tanstack.com/llms.txt · 主なエフェクトのページ:エフェクトが不要な場合もあります
React + TypeScript (FMC)
React の型付けとuseEffect の使用方針(命名、スコープ、使用を避ける場合)に関する FMC の規約です。ルーティングとサーバー I/O については、関連するスキル(以下)で扱います。
FMC スタック(必須)
| 要件 | ルール |
|---|---|
| React 19 | 19.x を対象とします。新しいコードでは forwardRef / useFormState / useActionState を使用しません。フォームには TanStack Form を使用します。 |
| React Compiler | アプリのビルドで有効化します。自動メモ化がデフォルトです。 |
| Lint | Oxlint の react プラグインを使用します。Compiler のネイティブルールはデフォルトで有効で、さらに 'react/unsupported-syntax': 'error' を設定します。eslint-plugin-react-hooks の jsPlugin は使用しません。 |
| メモ化 | デフォルトでは useMemo / useCallback / memo を追加しないでください。プロファイリングで必要と判明した場合、または lint ルールで必要な場合にのみ追加します。 |
| TanStack Start | サーバー I/O とミューテーション → tanstack-start-conventions(このスキルではコンポーネントの型付けのみを扱います) |
| データ取得 | ルートローダー + React Query を使用します。useEffect での fetch は使用しません(tanstack-router-conventions を参照)。 |
ドキュメント:React Compiler · React のルール · Oxc React Compiler サポート
関連スキル
| トピック | スキル |
|---|---|
ルート、ローダー、validateSearch | tanstack-router-conventions |
| URL の状態(ルーターの検索を優先) | tanstack-router-conventions |
| SSR、サーバー関数、Start のレイアウト | tanstack-start-conventions |
| フォルダー構成、薄いルート(Start) | スキル tanstack-start-conventions → references/app-structure.md |
createServerFn、サーバーミューテーション | スキル tanstack-start-conventions → references/server-functions.md |
| クライアントストア(context + reducer ではなく) | zustand-state-management |
| セマンティック HTML(残った div/span) | スキル unslop-code → references/semantic-html.md |
型付けに関する注意点
- Props:
React.ComponentPropsWithoutRef<'button'>(ref を除外)でネイティブ要素を拡張します。children: React.ReactNodeを使い、JSX.Elementは使わないでください。 - バリアントの props: 判別可能なユニオン(
{ variant: 'link'; href: string } | { variant: 'button'; onClick: () => void })を使い、判別子で型を絞り込みます。 - ref: React 19 では通常の prop です。新たに
forwardRefを使う必要はありません → react-19-patterns.md。 - イベント:
currentTargetに型が付くよう、具体的なReact.*Event<HTMLElement>型を使います。anyは使わないでください → event-handlers.md。 - フック:
null、[]、ユニオンには、useState<User | null>(null)のように型を明示します。DOM にはuseRef<HTMLInputElement>(null)と?.を、変更可能な値を保持するボックスにはuseRef(0)を使います。useReducerのアクションには判別可能なユニオンを使い、createContext<T | null>(null)と例外を投げるカスタムフックを組み合わせます。外部ストアにはuseSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)を使います。 - ルートフック:
createFileRouteコンポーネント内でRoute.useParams()/Route.useSearch()を使います。validateSearchはルートファイル内に記述します → tanstack-router.md。
useEffect — 命名、スコープ、使用を避ける場合
エフェクトは、外部システム(React 以外のウィジェット、ブラウザー API、マップ SDK、サブスクリプション、分析)との同期に使うための最終手段です。外部システムが関係しない場合、通常はエフェクトを使うべきではありません。
すべての useEffect に名前を付ける
useEffect には、無名のアロー関数ではなく、必ず名前付き関数式を渡します。カスタムフック内のエフェクトを含め、新規コードと編集するコードではこのスタイルを標準とします。複雑なクリーンアップ関数にも名前を付けます。
useEffect(
function updateDocumentTitle() {
document.title = `${count} items`
},
[count],
)
useEffect(function pollServerForUpdates() {
const intervalId = setInterval(/* ... */, 5000);
return function stopPollingServer() {
clearInterval(intervalId);
};
}, [serverId]);
命名からわかる兆候
命名は、キーボードに向かった状態で行う設計レビューです。詳細はeffects.mdを参照してください。
| シグナル | 意味 | 修正 |
|---|---|---|
| 名前に**「and」/「also」**が必要 | 1つのエフェクトに無関係な関心事が含まれている | それぞれ1つの名前で表せるよう、エフェクトを分割します |
syncDerivedValue、updateStateFromState、setXBasedOnY | 派生状態または状態間の同期 | レンダリング中に導出します |
notifyParentOfChange、reportStateToParent | 子の状態をきっかけに親が更新される | イベントハンドラーで呼び出す、データを上位に持ち上げる、またはコンポーネントを制御されたものにします |
resetFormOnSubmitFlag | 状態の受け渡しでユーザーの意図を表現 | イベントハンドラーで処理します |
persistOnClose、syncWhenClosed、saveDraftOnDismiss | 閉じる操作/UI状態の開閉に伴う副作用 | ハンドラーを使います。isOpen に対する Effect ではありません(effects.md §3) |
明確な外部操作を表す動詞:connectTo…、subscribeTo…、initialize…、synchronize…(ブラウザー、ネットワーク、マップ SDK) | Effect が正当な場合が多い | それでも名前を付け、必要に応じてクリーンアップを追加します |
正直な名前が React 内部の状態管理のように聞こえるなら、そのコードはレンダリング中、イベントハンドラー、または別のパターンに置くべきです。
クイックリファレンス
| 状況 | してはいけないこと | すること |
|---|---|---|
| props/state から派生する状態 | useState + useEffect | レンダリング中に計算する(デフォルトでは useMemo を使いません。Compiler は有効です) |
| prop の変更時に状態をリセット | useEffect と setState | key prop |
| ユーザーイベントへの応答 | 状態を監視する useEffect | イベントハンドラーから直接処理する |
| 閉じたときの永続化/同期 | isOpen を監視する useEffect | 閉じるハンドラー(または onCloseComplete) |
| 変更を親に通知する | onChange を呼び出す useEffect | イベントハンドラーから呼び出すか、制御コンポーネントを使う |
| 子から親へのデータ渡し | onFetched を呼び出す Effect | 親がフェッチして下位に渡す |
| 外部ストア | Effect + useState | useSyncExternalStore |
| データのフェッチ | useEffect | ルートローダー + React Query |
Strict Mode(開発時): マウント時に Effect のセットアップが 2 回実行されます。サブスクリプションとタイマーにはクリーンアップを追加してください。初回のみの初期化にはガードを設けてください(モジュールレベルの didInit ガード。 effects.md §6 を参照)。
Effect と React Compiler/lint
- Compiler は外部との同期に使う Effect を置き換えるものではありません。メモ化の変更によって Effect が再実行されるタイミングが変わる場合があります。Compiler を無効にするのではなく、Effect の設計(依存関係、分割、
useEffectEvent)を修正してください。 - lint:Oxlint のネイティブ React Compiler ルール(
react/set-state-in-effect、react/immutability、…)。
useEffectEvent
コールバックの一部で最新の props/state を読み取りながら、サブスクリプション(接続、インターバル、リスナー)を維持する必要がある Effect に使用します。例:effects.md §7。
- ユーザーに見えるイベントのような名前にします:
onConnected、onTickなど。onMount/onUpdateは避けてください。 - 呼び出せるのは Effect(または同じコンポーネント内の他の Effect Event)からのみです。レンダリング中、イベントハンドラー内、子への props としては使わないでください。
- 依存配列から省略します(lint によって強制されます)。Effect を再実行すべき依存関係を隠すために使ってはいけません。
React 19 の API(参照先)
| API | 用途 | ドキュメント |
|---|---|---|
use() | Promise/コンテキストの値を取り出す(サスペンドすることがあります) | use |
useOptimistic | 楽観的 UI | useOptimistic |
useTransition | 緊急性の低い更新 | useTransition |
useEffectEvent | Effect 内で安定したコールバック | useEffectEvent |
ルール
必ず守ること
- 名前付きの
useEffect関数を使います。React Compiler を有効にし、Oxlint のネイティブreact/*Compiler ルールを使います。 - 汎用的な
div/spanより意味的な HTML を優先します。スキルunslop-code→references/semantic-html.md - React 19 では
refを prop として使います。
決してしないこと
- 新たに
forwardRef/useFormState/useActionStateを使う(フォームには TanStack Form を使います) - 明確な理由なく、デフォルトで
useMemo/useCallback/memoを使う - アプリのデータ取得に
useEffectを使う(ローダー/Query を使います) - ストリーミングへの引き渡しで
use()に渡す Promise を await する
参考資料
- effects.md —
useEffectを記述またはレビューするとき、またはエフェクト名が上記の問題点一覧に該当するときに参照します(keyによるリセット、ジェスチャー時の永続化、親からのデータフロー、初期化ガード、useEffectEventの例) - event-handlers.md — クリック/送信/変更以外のイベントハンドラーの型を指定するとき、またはイベントを props 経由で転送するときに参照します
- react-19-patterns.md —
refを props として型指定するとき、use()を使うとき、またはレガシーなコードベースを React 19 に移行するときに参照します - generic-components.md — ジェネリックコンポーネントを記述するときに参照します(
HasIdで制約したT extends HasId、アクセサーを使ったSelect<T>) - tanstack-router.md — ルートフックを型指定するときに参照します(標準参照先:
tanstack-router-conventions→router-typescript.md) - Neciu Dan — useEffect 関数に名前を付け始めましょう