Claude Media
Claude CodeでReact 19をアップグレード — codemodの取りこぼしをSkillで拾う

Claude CodeでReact 19をアップグレード — codemodの取りこぼしをSkillで拾う

React 19の公式codemodが拾う変更と拾わない変更を仕分け、defaultProps・テストのact・型定義の残りをClaude Codeに洗い出させる手順です。再利用できるSKILL.mdの例つき。

React 19へのアップグレードは、公式のcodemodを先に当て、残った箇所をClaude Codeに洗い出させる順番で進めると作業しやすくなります。codemodは一部の変更しか書き換えません。defaultProps や forwardRef、テストまわりの警告は、実行後もコードに残ります。この記事では、codemodの対象範囲を表で仕分けたうえで、取りこぼしを探す手順を SKILL.md に切り出して使い回す形を紹介します。

先に18.3へ上げ、codemodを当てる順番

React 19のアップグレードガイドは、最初に react@18.3 へ上げることを勧めています。18.3は18.2と同一の中身で、React 19で必要になる変更について非推奨の警告を出すリリースです。警告の一覧がそのまま作業リストになります。

手順

アップグレードの流れ

  1. 1

    18.3に上げて警告を集める

    テストとビルドを回し、非推奨の警告をログに残します。

  2. 2

    React 19とcodemodを入れる

    react と react-dom を ^19.0.0 に、TypeScriptなら @types/react と @types/react-dom も ^19.0.0 にします。

  3. 3

    公式codemodを当てる

    JavaScriptの変更はレシピ、型の変更は types-react-codemod です。

  4. 4

    取りこぼしをClaude Codeで探す

    codemodの対象外を洗い出し、1件ずつ直します。

  5. 5

    テストとビルドで確かめる

    手順1と同じ条件で回し、警告が減ったことを確認します。

依存を更新するコマンドは、ガイドに次の形で載っています。

npm install --save-exact react@^19.0.0 react-dom@^19.0.0
npm install --save-exact @types/react@^19.0.0 @types/react-dom@^19.0.0

codemodの実行は2系統あります。作業ツリーをきれいにしてから、1つずつ当てます。

git status --short            # 何も出ない状態から始める
npx codemod@latest react/19/migration-recipe
npx types-react-codemod@latest preset-19 ./src
git diff --stat

react-codemod を直接使うより codemod コマンドのほうが、実行が速く、複雑な書き換えに対応し、TypeScriptへの対応も良いとされています。

codemodが書き換えるもの、残すもの

レシピ react/19/migration-recipe が実行するのは次の5つです。replace-reactdom-render、replace-string-ref、replace-act-import、replace-use-form-state、prop-types-typescript。ガイドは、この中にTypeScriptの変更は含まれないと明記しています。

ガイドの項目ごとに、codemodの記載の有無を並べると次のとおりです。

変更codemod人が見る範囲
ReactDOM.render / hydrate / unmountComponentAtNodecodemodあり(replace-reactdom-render)人が見る範囲書き換え後のルート生成の位置
文字列refcodemodあり(replace-string-ref)人が見る範囲クラスコンポーネントの置換結果
react-dom/test-utils の actcodemodあり(replace-act-import)人が見る範囲act 以外のtest-utils関数
propTypescodemodあり(prop-types-typescript)人が見る範囲型に置き換わったかの確認
関数コンポーネントの defaultPropscodemodガイドに記載なし人が見る範囲デフォルト引数への手作業の書き換え
findDOMNode、Legacy Contextcodemodガイドに記載なし人が見る範囲refやcontextTypeへの手作業の書き換え
型定義(useRef・JSX名前空間など)codemodtypes-react-codemod人が見る範囲サードパーティの型拡張

表の下3行が、Claude Codeに探させる領域です。

defaultPropsとpropTypesの残りを探す

propTypes は、React 19でチェックが外れ、書いてあっても黙って無視されます。エラーが出ないので、残っていても気づけません。defaultProps は関数コンポーネントでは使えなくなり、ES6のデフォルト引数に置き換える必要があります。クラスコンポーネントは代替がないため、defaultProps をそのまま使えます。

ガイドの例では、次のように書き換えます。

// After
interface Props {
  text?: string;
}
function Heading({text = 'Hello, world!'}: Props) {
  return <h1>{text}</h1>;
}

grepで候補を出すだけなら、Claude Codeを使わなくても済みます。ただし「関数コンポーネントかクラスか」の判定は、ファイルを開かないと決まりません。ここをClaude Codeに任せます。

grep -rnE "\.defaultProps\s*=" src
grep -rn "propTypes" src
grep -rn "forwardRef" src

forwardRef は、ガイドの破壊的変更の一覧に入っていません。React 19は ref をpropとして受け取れる仕様になり、代わりに element.ref へのアクセスが非推奨になりました。forwardRef の書き換えは必須ではなく、element.ref を読んでいる箇所の方が先に直す対象です。

テストのactとサードパーティの型定義を洗い出す

テストでは、react-dom/test-utils から act を読んでいると、ReactDOMTestUtils.act が非推奨で React.act を使うよう促す警告が出ます。replace-act-import が import {act} from 'react' に直します。

注意するのは act 以外の関数です。react-dom/test-utils のそれ以外の関数は削除され、呼ぶとエラーになります。codemodはこの部分を代替しないため、残りをgrepで探します。

grep -rnE "react-dom/test-utils|react-test-renderer" src tests

react-test-renderer はReact 19で非推奨になり、警告を出して並行レンダリングに切り替わります。ガイドの移行先は @testing-library/react と @testing-library/react-native です。

型まわりでは、types-react-codemod が拾えない自前の型拡張が残ります。たとえばグローバルな JSX 名前空間を拡張している global.d.ts は、declare module "react" で包む形に変わりました。モジュール名は tsconfig.json の jsx 設定で決まります。

  • "jsx": "react-jsx" なら react/jsx-runtime
  • "jsx": "react-jsxdev" なら react/jsx-dev-runtime
  • "jsx": "react" または "preserve" なら react

ほかに、useRef が引数必須になった点、refコールバックが暗黙のreturnを返すと型エラーになる点があります。前者は refobject-defaults、後者は no-implicit-ref-callback-return として、プリセットに含まれます。ReactElement の props は、型引数なしだと any ではなく unknown になりました。要素のpropsを直接読んでいたコードは、tsc が型エラーとして示します。useReducer も、型引数にreducer全体の型を渡す書き方ができなくなり、型引数なしで書くのが新しい推奨です。どちらもエラー箇所が tsc --noEmit の出力に並ぶので、そのままClaude Codeへ渡せます。

サードパーティのライブラリ側が内部APIに依存していると、ガイドは「アップグレードを妨げる可能性がある」と書いています。依存パッケージの対応状況は、npm ls react の出力で見つかる警告から手がかりを得ます。

grepでは見つからない実行時の変更

ここまでの残件は、文字列で検索できる書き方でした。React 19には、コードを書き換えなくても挙動が変わる項目もあります。スキルの手順2では、こちらを「要確認」の欄に並べさせます。

  • レンダー中のエラー: 再スローされなくなりました。Error Boundaryで捕まらないエラーは window.reportError、捕まったエラーは console.error に出ます。本番のエラー報告が再スローに依存している場合は、createRoot と hydrateRoot のオプション onUncaughtError と onCaughtError に報告処理を移します
  • 新しいJSX変換: 古い変換のままだと、React 19では警告が出ます。ガイドは、多くの環境で有効になっているため影響は小さいとしています
  • Strict Mode: 開発中の二重レンダリングで、useMemo と useCallback が1回目の結果を再利用します。初期マウントではrefコールバックも二重に呼ばれます
  • UMDビルドの廃止: スクリプトタグで読み込んでいるページは、ESMベースのCDNに切り替えます

これらはテストを回して初めて分かる種類の変化です。Claude Codeに「エラー報告の呼び出し元を探して」と頼み、報告処理が onUncaughtError に載っているかを読ませると、見落としを減らせます。

取りこぼし探しをSKILL.mdに切り出す

ここまでのgrepと判定を毎回プロンプトで打つのは手間です。.claude/skills/ に置いておけば、同じ手順を別のリポジトリや次の担当者でも使えます。

プロジェクトのスキルは .claude/skills/<名前>/SKILL.md に置きます。リポジトリにコミットすれば、チームの全員が同じ手順を使えます。例えば次のような形です。

---
name: react19-leftovers
description: React 19のcodemod実行後に残る非対応箇所を洗い出す。codemodを当てた後、またはReact 19移行の残件確認を頼まれたときに使う。
disable-model-invocation: true
argument-hint: "[対象ディレクトリ]"
allowed-tools: Bash(grep *) Bash(git diff *) Bash(npx tsc *)
---
 
## 作業ツリーの状態
 
!`git status --short`
 
## 手順
 
対象は $ARGUMENTS 。未指定なら src 。
 
1. 次のパターンを検索し、ファイルごとに分類する。
   - `.defaultProps =` : 関数コンポーネントとクラスを区別
   - `propTypes`
   - `forwardRef` と `element.ref`
   - `react-dom/test-utils` と `react-test-renderer`
   - `declare global` と `namespace JSX`
2. 分類ごとに、直す・直さない・要確認を表にして示す。
3. 直すと決めたものだけ編集し、`npx tsc --noEmit` を実行する。
4. 実行結果を要約し、直さなかった箇所の理由を残す。

このSKILL.mdで押さえている点は3つです。

  • disable-model-invocation: true: 副作用のある作業なので、手動の /react19-leftovers でだけ動かす
  • ! で始まるコマンド: git status --short の出力がスキル本文に展開され、作業ツリーの状態を最初から渡せる
  • $ARGUMENTS: 呼び出し時に打った文字列が入り、対象ディレクトリを切り替えられる

allowed-tools は、そのスキルを呼び出したターンだけ有効です。次のメッセージを送ると解除されます。スキルの本文を長くしすぎない点も重要で、公式は500行以下を勧めています。個別の判定基準が増えたら、checklist.md のような補助ファイルに分けて SKILL.md から参照させます。

編集の途中でも、スキルの変更は同じセッション内で反映されます。再起動は不要です。

個人用に ~/.claude/skills/ へ置いた場合は、全プロジェクトで使えます。ただしCoworkやクラウドのセッションでは読み込まれません。複数のリポジトリで同じ移行をするなら、個人用に置くか、プラグインにまとめて配る選択肢があります。

つまずきやすい箇所

落とし穴

警告が消えても移行は終わっていない

propTypes はReact 19では黙って無視されるため、テストの失敗として表に出ません。grepで残りを数え、0件になるまで確認します。

落とし穴

codemodが全体を書き換えて差分が膨らむ

レシピとtypes-react-codemodを同じコミットで当てると、レビューしづらくなります。codemodごとに分けてコミットすれば、問題の切り分けも楽です。

落とし穴

クラスコンポーネントのdefaultPropsまで消してしまう

クラスコンポーネントの defaultProps は、React 19でも使えます。スキルの手順で「関数かクラスか」を区別させておけば、不要な書き換えを避けられます。

CLAUDE.mdに残しておく規約

移行の途中でセッションが変わっても、方針がぶれないよう、リポジトリの CLAUDE.md に短く書いておきます。

## React 19移行の方針
- 関数コンポーネントの defaultProps はデフォルト引数に置き換える
- クラスコンポーネントの defaultProps はそのまま残す
- 変更は codemod ごとに別コミットにする
- 最後に npx tsc --noEmit とテストを全件実行して、結果を貼る

テストの回し方は、Claude CodeでJest 30へアップグレードと同じ型で使えます。ベースラインの通過数を先に記録して、移行後に減っていないかを比べる進め方です。スキルのfrontmatter全体はClaude Code Skillsのfrontmatter全フィールドにまとめています。ルーターの型生成と合わせて検証する場合はClaude CodeでReact Router v7アプリを開発するも参考になります。

完了の判定

移行が終わったかどうかは、次の4点で判断します。

  • 18.3で出ていた非推奨の警告が、React 19でも出ない
  • 上のgrepの結果が、意図して残した箇所(クラスの defaultProps など)だけになる
  • npx tsc --noEmit が通る
  • テストの通過数が、移行前のベースラインから減っていない

最後の1点は、テストの削除やスキップが紛れていないかを git diff で確かめるところまで含めます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. react.dev/blog/2024/04/25/react-19-upgrade-guide
  2. code.claude.com/docs/en/skills
この記事を共有:XはてブLinkedIn