キャラクタリゼーションテスト、小さな安全なステップ、状態の整理で Claude Code を使いながら React コンポーネントを振る舞いを変えずにリファクタする方法を学びます。

React のリファクタは怖く感じられます。多くのコンポーネントは小さくきれいな部品ではなく、UI、state、effects、そして「あともう一つ prop」が混ざった生きた塊になっているからです。構造を変えると、意図せずタイミングや識別、データフローが変わることがよくあります。
リファクタで振る舞いが変わるのは、多くの場合偶発的に以下を起こすときです:
key が変わって state がリセットされる。「掃除」と「改善」が混ざると、リファクタが書き換えに変わります。コンポーネントを抽出してから名前を一斉に変え、state の形を直してフックを置き換える――そのうちレイアウトもロジックも同時に変わってしまい、どの変更が原因か分からなくなります。
安全なリファクタは一つの約束を守ります:ユーザーにとって振る舞いは同じで、結果的にコードが読みやすくなること。props、イベント、読み込み状態、エラー状態、エッジケースは同じように振る舞うべきです。振る舞いを変えるなら、それは意図的で小さく、明示されているべきです。
Claude Code(または任意のコーディングアシスタント)で React コンポーネントをリファクタするなら、それを自動操縦ではなく高速なペアプログラマとして扱ってください。編集前にリスクを説明させ、小さなステップの計画を提案させ、振る舞いが同じであることをどのように検証したかを説明させます。そして自分でも検証してください:アプリを動かし、奇妙な経路をクリックし、そのコンポーネントが今日どう振る舞うかを捉えたテストに頼りましょう。
時間を浪費している一つのコンポーネントを選びます。ページ全体や曖昧な「UI 層」ではなく、読みづらく変更しづらい、あるいはもろい state と副作用でいっぱいの単一のコンポーネントです。ターゲットが絞れているほど、アシスタントの提案も検証しやすくなります。
5 分で確認できるゴールを書いてください。良いゴールは結果ではなく構造に関するものです:「小さなコンポーネントに分割する」「state を追いやすくする」「モックなしでテストできるようにする」など。具体的な指標や既知のボトルネックがない限り「より良くする」や「パフォーマンスを上げる」といった目標は避けてください。
エディタを開く前に境界を設定します。安全なリファクタのルールは退屈です:
次に、コードを移動するときに静かに振る舞いを壊す依存関係を列挙します:API コール、context プロバイダ、ルーティングのパラメータ、機能フラグ、アナリティクスイベント、共有グローバル状態などです。
具体例:600 行の OrdersTable があり、データをフェッチし、フィルタし、選択を管理し、詳細を表示するドロワーがあるとします。明確なゴールは「行描画とドロワー UI をコンポーネントに抽出し、選択状態を一つの reducer に移す。UI は変えない。」のようにします。これで「完了」の定義と範囲外がはっきりします。
リファクタする前に、そのコンポーネントをブラックボックスとして扱い、今日それが何をするかを記録します。あなたの仕事は「今これがすること」を捉えることであり、理想の仕様を推測することではありません。これがリファクタを設計変更に変えるのを防ぎます。
まず現在の振る舞いを平易な言葉で書き出します:与えられた入力に対して UI は何を出すか。props、URL パラメータ、機能フラグ、context やストアから来るデータも含めます。Claude Code を使うなら、小さく焦点を絞ったスニペットを貼って、後で確認できる正確な文に言い直してもらってください。
人が実際に目にする UI 状態をカバーします。コンポーネントはハッピーパスで見た目が大丈夫でも、読み込み中、空、エラーで壊れることがあります。
見落としがちな暗黙のルールも書き出してください。リファクタで壊れやすいものです:
例:ユーザーテーブルが結果をロードし、検索をサポートし、「最終アクティブ」を基準にソートする場合、検索が空のとき何が起きるか、API が空リストを返したとき、API がエラーを返したとき、同じ「最終アクティブ」時刻を持つ 2 人のユーザーがいるときにどう表示されるかを書き出します。ソートが大文字小文字を区別するか、フィルター変更時に現在のページを保持するかなどの細かい点も含めます。
ノートが退屈で具体的に感じられたら、準備完了です。
キャラクタリゼーションテストは「今これがすること」テストです。奇妙だったり一貫性がない振る舞いでも、そのまま記述します。逆説的ですが、これがリファクタがこっそり書き換えに変わるのを防ぐ鍵です。
Claude Code で React コンポーネントをリファクタするなら、これらのテストが安全帯になります。ツールはコードの形を変えるのを手伝えますが、何を変えてはいけないかを決めるのはあなたです。
ユーザー(や他のコード)が依存しているものに焦点を当てます:
テストは実装ではなく結果をアサートしてください。setState が呼ばれた や このフックが走った のような実装依存の主張ではなく、「Save ボタンが無効になりメッセージが表示される」のように UI 結果を検証します。コンポーネント名を変えたりフック順序を入れ替えたために壊れるテストは振る舞いを守れていません。
非同期の振る舞いはリファクタでタイミングが変わりやすいので明示的に扱ってください:UI が落ち着くのを待ってからアサートします。タイマー(デバウンス検索、遅延トースト)がある場合は偽のタイマーを使って時間を進めます。ネットワーク呼び出しがある場合はフェッチをモックして、成功後と失敗後にユーザーが見るものをアサートします。Suspense のようなフローではフォールバックと解決後のビューの両方をテストします。
例:"Users" テーブルが検索完了後にのみ「結果なし」を表示するなら、キャラクタリゼーションテストはその順序を固定します:まずローディング表示、次に行か空メッセージのどちらかが表示される、という順序を守るテストです。
目的は「大きな変更を速くすること」ではなく、コンポーネントが何をするかの明確な絵を得て、振る舞いを安定させながら一度に一つずつ小さく変えることです。
まずコンポーネントを貼り付け、責務を平易な英語(あるいは日本語)で要約させます。具体的に:どのデータを表示するか、どのユーザー操作を扱うか、どんな副作用を発生させるか(フェッチ、タイマー、購読、アナリティクス)を尋ねてください。これでリファクタが危険になる隠れた仕事が見えてくることがよくあります。
次に依存関係マップを出させます:props、context 読み取り、カスタムフック、ローカル state、派生値、effects、モジュールレベルのヘルパーなど、すべての入力と出力の棚卸しです。安全に移動できるもの(純粋な計算)と「くっついている」もの(タイミング、DOM、ネットワーク)を指摘してもらうと便利です。
その後、抽出候補を提案させます。ただし厳格なルールを一つ守らせます:プレゼンテーション(純粋表示)部分と状態を持つコントローラ部分を分離すること。JSX が多く純粋に props を必要とする箇所は最初の良い抽出対象です。イベントハンドラ、非同期呼び出し、state 更新が混ざっている部分は後回しにします。
現実に耐えるワークフロー:
チェックポイントが重要です。Claude Code に最小の計画を書かせ、各ステップがコミットできて戻せるようにします。実用的なチェックポイント例:「\<TableHeader`>` をロジック変更なしで抽出する」などです(注:コード部分はそのままに)。
具体例:顧客テーブルをレンダリングし、フィルタを制御し、データをフェッチするコンポーネントがあるなら、最初にテーブルのマークアップ(ヘッダ、行、空状態)を純粋コンポーネントに抽出します。フィルター state やフェッチ effect はその後に移動します。この順序だと JSX と共にバグが移動しにくくなります。
大きなコンポーネントを分割するとき、リスクは JSX を移すこと自体ではなくデータフロー、タイミング、イベントの配線を変えてしまうことです。まずは「コピーして配線する」作業を行い、後でクリーンアップするという方針を取ります。
UI に既にある境界を探します。文脈ではなく UI の中に「それ自体を説明できる部分」があるか見つけます:アクション付きヘッダー、フィルタバー、結果リスト、ページネーションのフッタなど。
安全な最初の動きは純粋なプレゼンテーションコンポーネントを抽出することです:props in、JSX out。わざと退屈に保ちます。新しい state、effect、API コールを導入しないでください。元のコンポーネントにあったクリックハンドラが三つのことをしていたら、親に残して渡します。
よく機能する境界:ヘッダー、リストと行アイテム、フィルタ(入力のみ)、フッタ(ページネーション、合計、バルクアクション)、ダイアログ(開閉とコールバックを渡す)などです。
名前付けは重要です。UsersTableHeader や InvoiceRowActions のように具体的な名前を選んでください。Utils や HelperComponent のような雑な名前は責務を隠し、関心の混在を招きます。
コンテナコンポーネントは本当に必要なときだけ導入します:UI の一部が状態や副作用を所有しないと一貫性が保てない場合だけです。その場合でも狭く保ち、一つの目的(例:フィルタ状態)だけを所有し、他は props として渡します。
ややこしいコンポーネントは通常、次の 3 種類のデータを混ぜています:ユーザーが編集する実際の UI state、計算で得られる派生データ、サーバの state(ネットワークから来るもの)。これらをすべてローカル state として扱うと、リファクタでいつ更新されるかが変わりやすくなります。
まず各データ片にラベルを付けます。ユーザーが編集するか、props/state/フェッチされたデータから計算できるかを判断してください。また、その値はここで所有されているか、ただ通過しているだけかも問います。
派生値は useState に置かないで、小さな関数に移すか、計算コストが高ければ useMemo に入れます。これで state 更新が減り、振る舞いの予測がしやすくなります。
安全なパターン:
useState にはユーザーが編集する値だけを残す。useMemo で包む。effect は多くの仕事をしたり誤った依存に反応したりすると壊れます。目的ごとに一つの effect を目指してください:localStorage 同期用に一つ、フェッチ用に一つ、購読用に一つなど。多くの値を読む effect は余分な責務を隠していることが多いです。
Claude Code に小さな変更を頼むときは、例えば「ある effect を二つに分ける」「一つの責務をヘルパーに移す」など小さな指示を出し、各移動の後にキャラクタリゼーションテストを実行してください。
prop drilling を注意深く扱ってください。コンテキストで置き換えるのは、配線を減らして所有権が明確になる場合に限ります。コンテキストが適切なのは current user、theme、feature flags のようなアプリレベルの概念であって、一つのコンポーネントツリーのワークアラウンドであってはいけません。
例:テーブルコンポーネントが rows と filteredRows の両方を state に持っているなら、rows を state に保持し、filteredRows は rows と query から計算して純粋関数にしておく、というのが安全です。
リファクタが失敗する多くの原因は、変化に気づく前にあまりにも多くを変えてしまうことです。解決はシンプル:小さなチェックポイントで作業し、各チェックポイントをミニリリースのように扱うこと。たとえ一つのブランチで作業していても、PR サイズの変更にしておけば何が壊れたか分かりやすくなります。
有意義な動きを一つするたびに止まり、振る舞いを変えていないことを証明します。その証明は自動化(テスト)でも手動(ブラウザでの簡単チェック)でも構いません。目標は完璧ではなく、早期検出です。
実用的なチェックポイントループ:
Koder.ai などのプラットフォームを使っているなら、スナップショットとロールバックは比較対象を作ったり実験が外れたときに便利です。それでも通常のコミットは続けてください。
単純な振る舞いの帳簿をつけながら進めると、同じチェックを繰り返すのを防げます。帳簿は検証したことの短いメモです。
例:
何かが壊れたら、帳簿が再チェックする項目を示し、チェックポイントのおかげですばやく巻き戻せます。
多くのリファクタは小さく退屈な部分で失敗します。UI は動くけれど、余白ルールが消えたり、クリックハンドラが二重で発火したり、入力中にフォーカスが失われたりします。アシスタントはコードをきれいに見せるため、振る舞いがずれても気づきにくくなる場合があります。
よくある原因は構造の変更です。コンポーネントを抽出して余分な <div> でラップしたり、<button> をクリック可能な <div> に置き換えたりすると、CSS セレクタ、レイアウト、キーボードナビゲーション、テストクエリが変わってしまいます。意図的に変えるまで同じタグとデータ属性を保ってください。
振る舞いを壊しやすい罠:
useEffect / useMemo / useCallback に移すと依存関係のミスで古い値を参照したりループを起こす。もともと「クリック時に動いていた」処理を「何かが変わるたびに動く」ようにしないでください。具体例:テーブルコンポーネントを分割して行のキーを ID から配列インデックスに変えると、一見問題ないように見えて行が並べ替えられたときに選択状態が壊れることがあります。「きれい」はボーナスであり、「同じ振る舞い」が要件です。
マージ前にリファクタが振る舞いを保ったことの証拠が欲しいです。最も簡単な指標は退屈です:古いテストが修正なしで通り、新しいキャラクタリゼーションテストも通ること。
最終的な簡易チェック:
onChange がマウント時に発火しない)簡単なサニティチェック:コンポーネントを開いてエラーを発生させ、リトライし、フィルターをクリアするなど一つ変な流れを試してください。メインパスが動いていても遷移で壊れることがあります。
もし何か失敗したら、最後の変更を戻してより小さなステップでやり直すのが、巨大な差分をデバッグするより速いことが多いです。
ProductTable がデータフェッチ、フィルタ管理、ページネーション、削除確認ダイアログ、行操作(編集、複製、アーカイブ)まで全部やっていると想像してください。小さく始まり徐々に 900 行のファイルになった典型例です。
症状はよくあるものです:useState があちこちに散らばり、useEffect が特定の順序で走り、ある「 harmless 」な変更がフィルターが有効なときだけページネーションを壊す。予測不能に感じられて誰も触らなくなります。
構造を変える前に、いくつかの React キャラクタリゼーションテストで振る舞いを固定します。内部 state ではなくユーザーがすることに焦点を合わせます:
これで小さなコミットでリファクタできます。抽出計画の一例:FilterBar(コントロールを描画しフィルター変更を emit)、TableView(行とページネーションを描画)、RowActions(アクションメニューと確認ダイアログの UI を所有)、useProductTable フックが煩雑なロジック(クエリパラメータ、派生 state、副作用)を所有する。
順序が重要です。まずダムな UI(TableView、FilterBar)を抽出して props を変えずに渡す。リスクの高い部分(状態と effect の移動)は最後に残します。移動するときは元の prop 名とイベント形を保ち、テストが通るようにします。テストが壊れたら、それはスタイルの問題ではなく振る舞いの変更を見つけた証拠です。
Claude Code での React コンポーネントのリファクタを毎回安全に感じたいなら、今やったことを小さなテンプレートにしてください。目的はプロセスを増やすことではなく、驚きを減らすことです。
どんなコンポーネントでも追従できる短いプレイブックを書きます:
スニペットとしてノートやリポジトリに置いておくと、次回のリファクタが同じ安全策で始まります。
コンポーネントが安定して読みやすくなったら、ユーザーへの影響に基づいて次のパスを選びます。一般的な順序は:アクセシビリティ(ラベル、フォーカス、キーボード)、パフォーマンス(メモ化、重いレンダリングの改善)、クリーンアップ(型付け、命名、不要コード)です。これらを一つの PR に混ぜないでください。
Koder.ai(koder.ai)のようなツールを使う場合は、プランニングモードで手順をまとめ、スナップショットとロールバックをチェックポイントとして使うと便利です。終わったらソースをエクスポートして差分を確認し、履歴をきれいに保ちましょう。
テストが怖かった振る舞いをカバーしている、次の変更が新機能か製品の決定になる、あるいは同じ PR 内で命名や型を完璧にしたい欲求が湧いたら止めて出荷してください。残った改善点は短いバックログとして記録しておきます。
React のリファクタが見た目は同じでも壊れるのは、識別(identity) や タイミング が気づかないうちに変わってしまうからです。よくある振る舞いの破壊例:
key の変更で state がリセットされる。構造の変更はテストで証明されるまで振る舞いの変更だと仮定してください。
構造に関する、短く検証しやすいゴールを立ててください。構造に焦点を当て、改善や性能向上のような曖昧な目標は避けます。良いゴールの例:
ヘッダー、行、ドロワーをコンポーネントに分割し、UI は一切変えない。選択状態を一つの reducer に移して、イベントや props を変えない。「もっと良くする」といった目標は、具体的な指標と既知のボトルネックがある場合のみ採用してください。
コンポーネントをブラックボックスとして扱い、ユーザーから見える振る舞いを書き出します:
書き出した内容が退屈で具体的なら、それは有用です。
現在のコンポーネントが“今日どう振る舞うか”を記述する キャラクタリゼーションテスト を追加します。奇妙だったり不揃いな振る舞いでも、そのまま閉じ込めておきます。
テストの重点:
実装の呼び出しではなく、UI の結果をアサートしてください。非同期の流れは明示的に扱い、UI が落ち着くのを待ってから検証します。
Claude Code(あるいは任意のアシスタント)を使うときは、注意深いペアプログラマとして扱ってください:
大きな「書き換え」差分は受け入れず、検証できる増分変更を要求してください。
まずは純粋なプレゼンテーション部分から抽出を始めます:
コピーして接続するだけの最初の作業を行い、リファクタ後に内部をきれいにしていきます。UI を分割してから状態や副作用へ取りかかるのが安全です。
実体(ID)に基づく安定した key を使ってください。配列のインデックスを key に使うと、並べ替えやフィルター、挿入・削除で問題が出ます。起こりうるバグの例:
key を変更するようなリファクタはハイリスクと扱い、並べ替えケースを必ずテストしてください。
導出データは useState に入れないで、既存の入力から計算するようにしましょう。
安全な方針:
useState に残すfilteredRows)は rows と query から計算するuseMemo を使うこうすると更新の不具合が減り、考えやすいコンポーネントになります。
チェックポイントを使って、小さなステップごとに簡単に戻せるようにします:
Koder.ai のスナップショットとロールバックは、実験的な変更を比較する際に補助になります。
振る舞いが固定されていて、コードが明らかに変更しやすくなったと感じたら止めて公開します。良い終了シグナル:
リファクタをマージし、アクセシビリティや性能、クリーンアップは別の仕事として残してください。