React 19 RC アップグレードガイド

April 25, 2024 by Ricky Hanlon


React 19 に追加された改善にはいくつかの破壊的変更が必要ですが、アップグレードをできるだけスムーズに行えるよう努力しているため、ほとんどのアプリには影響が出ないことを予想しています。

補足

React 18.3 も公開されました

React 19 へのアップグレードを容易にするため、react@18.3 リリースを公開しました。これは 18.2 とほぼ同一ですが、非推奨化 API や React 19 に向けて必要なその他の変更に対する警告が追加されています。

React 19 にアップグレードする前に、問題点を見つけるためにまず React 18.3 にアップグレードすることをお勧めします。

18.3 における変更点については、リリースノートをご覧ください。

この投稿では、React 19 にアップグレードする手順をご案内します。

React 19 をテストしていただける方は、このアップグレードガイドに従い、遭遇した問題を報告してください。React 19 に追加された新機能のリストについては、React 19 リリースのお知らせをご覧ください。


インストール

補足

新しい JSX トランスフォームの必須化

2020 年に、バンドルサイズを改善し、React をインポートせずに JSX を使用できるようにするための新しい JSX トランスフォームを導入しました。React 19 では、ref を props として使用できるようにしたり JSX の速度を向上させたりといった追加の改善を行っており、これらには新しいトランスフォームが必須です。

新しいトランスフォームが有効になっていない場合、次の警告が表示されます。

Console
Your app (or one of its dependencies) is using an outdated JSX transform. Update to the modern JSX transform for faster performance: https://react.dev/link/new-jsx-transform

ほとんどの環境ではこのトランスフォームがすでに有効になっているため、ほとんどのアプリは影響を受けないと予想されます。手動でアップグレードする方法については、告知記事をご覧ください。

React と React DOM の最新バージョンをインストールするには以下のようにします。

npm install react@rc react-dom@rc

TypeScript を使用している場合は、型も更新する必要があります。React 19 が安定版としてリリースされた後は、通常通り @types/react@types/react-dom から型をインストールできます。安定版になるまでは package.json で強制的に別のパッケージを指定することで、新しい型を利用できます。

{
"dependencies": {
"@types/react": "npm:types-react@rc",
"@types/react-dom": "npm:types-react-dom@rc"
},
"overrides": {
"@types/react": "npm:types-react@rc",
"@types/react-dom": "npm:types-react-dom@rc"
}
}

また、最も一般的な書き換えのための codemod も含まれています。下記の TypeScript 関連の変更を参照してください。

codemod

アップグレードを支援するため、codemod.com のチームと協力し、React 19 の新しい API やパターンにコードを自動的に更新するための codemod を公開しました。

すべての codemod は react-codemod リポジトリで利用可能であり、Codemod チームも codemod の保守に参加しています。これらの codemod を実行するには、react-codemod コマンドではなく codemod コマンドの使用をお勧めします。こちらのコマンドの方が高速に実行され、複雑なコードの移行を処理でき、TypeScript のサポートもより良好です。

補足

React 19 関連の codemod をすべて実行

このガイドにある codemode をすべて実行するには React 19 の codemod レシピを以下のように実行します。

npx codemod@latest react/19/migration-recipe

これにより以下の react-codemod の codemod が実行されます。

これには TypeScript 関連の変更は含まれていません。以下の TypeScript 関連の変更を参照してください。

変更のうち codemod が利用できるものは以下で紹介されています。

利用可能なすべての codemod の一覧については、react-codemod リポジトリを参照してください。

破壊的変更

レンダー中のエラーは再スローされない

これまでのバージョンの React では、レンダー中にスローされたエラーはキャッチされた後に再スローされていました。開発環境では、console.error にもログを出力していたため、エラーログの重複が発生していました。

React 19 では、重複を減らすためにエラーの扱いを改善し、再スローは行わないようになりました。

  • キャッチされないエラー:エラーバウンダリによってキャッチされないエラーは window.reportError に報告されます。
  • キャッチされたエラー:エラーバウンダリによってキャッチされたエラーは console.error に報告されます。

この変更はほとんどのアプリに影響を与えないはずですが、本番環境におけるエラーレポートシステムが再スローの挙動に依存している場合は、エラー処理を更新する必要があります。これをサポートするために、カスタムのエラー処理を行うための新しい手段を createRoothydrateRoot に追加しました。

const root = createRoot(container, {
onUncaughtError: (error, errorInfo) => {
// ... log error report
},
onCaughtError: (error, errorInfo) => {
// ... log error report
}
});

詳細は、createRoothydrateRoot のドキュメントを参照してください。

非推奨化 React API の削除

廃止:関数コンポーネントの propTypesdefaultProps

PropTypes2017 年 4 月 (v15.5.0) に非推奨化されました。

React 19 では、React パッケージから propTypes チェックが削除されており、使用しても無視されるようになります。propTypes を使用している場合は、TypeScript または他の型チェックソリューションへの移行をお勧めします。

また、関数コンポーネントから defaultProps を削除します。ES6 のデフォルトパラメータを代わりに使用してください。クラスコンポーネントでは ES6 による代替手段がないため、defaultProps を引き続きサポートします。

// Before
import PropTypes from 'prop-types';

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

補足

codemod で以下のように propTypes を TypeScript に変換できます。

npx codemod@latest react/prop-types-typescript

廃止:contextTypesgetChildContext を使用したレガシーコンテクスト

レガシーコンテクストは 2018 年 10 月 (v16.6.0) に非推奨化されました。

レガシーコンテクストは、contextTypesgetChildContext を使用するクラスコンポーネントでのみ利用可能であり、見逃しやすい微妙なバグのため、のちに contextType に置き換えられました。React 19 では、React を少し小さく、速くするためにレガシーコンテクストを削除しています。

クラスコンポーネントでまだレガシーコンテクストを使用している場合、新しい contextType API に移行する必要があります。

// Before
import PropTypes from 'prop-types';

class Parent extends React.Component {
static childContextTypes = {
foo: PropTypes.string.isRequired,
};

getChildContext() {
return { foo: 'bar' };
}

render() {
return <Child />;
}
}

class Child extends React.Component {
static contextTypes = {
foo: PropTypes.string.isRequired,
};

render() {
return <div>{this.context.foo}</div>;
}
}
// After
const FooContext = React.createContext();

class Parent extends React.Component {
render() {
return (
<FooContext value='bar'>
<Child />
</FooContext>
);
}
}

class Child extends React.Component {
static contextType = FooContext;

render() {
return <div>{this.context}</div>;
}
}

削除:文字列形式の ref

文字列形式の ref は 2018 年 3 月 (v16.3.0) に非推奨化されました。

いくつかの問題のためコールバック形式の ref に置き換えられるまで、クラスコンポーネントは文字列形式の ref をサポートしていました。React 19 では、React をよりシンプルで理解しやすくするため、文字列形式の ref を削除します。

クラスコンポーネントでまだ文字列形式の ref を使用している場合は、コールバック形式の ref に移行する必要があります。

// Before
class MyComponent extends React.Component {
componentDidMount() {
this.refs.input.focus();
}

render() {
return <input ref='input' />;
}
}
// After
class MyComponent extends React.Component {
componentDidMount() {
this.input.focus();
}

render() {
return <input ref={input => this.input = input} />;
}
}

補足

codemod で以下のように文字列形式の ref をコールバック形式の ref に変換できます。

npx codemod@latest react/19/replace-string-ref

削除:モジュールパターンファクトリ

モジュールパターンファクトリは 2019 年 8 月 (v16.9.0) に非推奨化されました。

このパターンはほとんど使用されておらず、サポートすることで React がわずかに大きく、遅くなる原因となっています。React 19 ではモジュールパターンファクトリのサポートが削除されており、通常の関数に移行する必要があります。

// Before
function FactoryComponent() {
return { render() { return <div />; } }
}
// After
function FactoryComponent() {
return <div />;
}

削除:React.createFactory

createFactory2020 年 2 月 (v16.13.0) に非推奨化されました。

JSX が広範にサポートされる以前は createFactory の使用が一般的でしたが、今ではほとんど使用されておらず、JSX に置き換えることができます。React 19 では createFactory が削除されており、JSX に移行する必要があります。

// Before
import { createFactory } from 'react';

const button = createFactory('button');
// After
const button = <button />;

削除:react-test-renderer/shallow

React 18 において、react-test-renderer/shallow を更新して react-shallow-renderer を再エクスポートするようにしていました。React 19 では、react-test-render/shallow が削除されており、代わりにこのパッケージを直接インストールするようになります。

npm install react-shallow-renderer --save-dev
- import ShallowRenderer from 'react-test-renderer/shallow';
+ import ShallowRenderer from 'react-shallow-renderer';

補足

シャローレンダリングの再考を

シャローレンダリングは React の内部構造に依存しており、将来のアップグレードの妨げとなる可能性があります。テストを @testing-library/react@testing-library/react-native に移行することをお勧めします。

非推奨化 React DOM API の削除

削除:react-dom/test-utils

actreact-dom/test-utils から react パッケージに移動しました。

Console
ReactDOMTestUtils.act is deprecated in favor of React.act. Import act from react instead of react-dom/test-utils. See https://react.dev/warnings/react-dom-test-utils for more info.

この警告を修正するには、actreact からインポートするようにします。

- import {act} from 'react-dom/test-utils'
+ import {act} from 'react';

その他の test-utils 関数はすべて削除されました。これらのユーティリティは一般的ではなく、コンポーネントや React の低レベルな内部実装の詳細へ依存しやすくなってしまうものでした。React 19 でこれらの関数を呼び出すとエラーとなります。将来のバージョンではエクスポートが削除されます。

代替手段については、警告ページをご覧ください。

補足

codemod で以下のように ReactDOMTestUtils.actReact.act に変換できます。

npx codemod@latest react/19/replace-act-import

削除:ReactDOM.render

ReactDOM.render2022 年 3 月 (v18.0.0) に非推奨化されました。React 19 では ReactDOM.render が削除されており、ReactDOM.createRoot を使用するよう移行する必要があります。

// Before
import {render} from 'react-dom';
render(<App />, document.getElementById('root'));

// After
import {createRoot} from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);

補足

codemod で以下のように ReactDOM.renderReactDOM.createRoot に変換できます。

npx codemod@latest react/19/replace-reactdom-render

削除:ReactDOM.hydrate

ReactDOM.hydrate2022 年 3 月 (v18.0.0) に非推奨化されました。React 19 では ReactDOM.hydrate が削除されており、ReactDOM.hydrateRoot を使用するよう移行する必要があります。

// Before
import {hydrate} from 'react-dom';
hydrate(<App />, document.getElementById('root'));

// After
import {hydrateRoot} from 'react-dom/client';
hydrateRoot(document.getElementById('root'), <App />);

削除:unmountComponentAtNode

ReactDOM.unmountComponentAtNode2022 年 3 月 (v18.0.0) に非推奨化されました。React 19 では、root.unmount() を使用するよう移行する必要があります。

// Before
unmountComponentAtNode(document.getElementById('root'));

// After
root.unmount();

詳細については、createRoothydrateRootroot.unmount() をご覧ください。

削除:ReactDOM.findDOMNode

ReactDOM.findDOMNode2018 年 10 月 (v16.6.0) に非推奨化されました。

findDOMNode はレガシーな避難ハッチであり、実行速度が遅く、リファクタリングが困難で、最初の子要素しか返せず、抽象化レイヤーを破壊するといった問題があるため(詳細はこちら)、削除されます。ReactDOM.findDOMNodeDOM 用の ref で置き換えることができます。

// Before
import {findDOMNode} from 'react-dom';

function AutoselectingInput() {
useEffect(() => {
const input = findDOMNode(this);
input.select()
}, []);

return <input defaultValue="Hello" />;
}
// After
function AutoselectingInput() {
const ref = useRef(null);
useEffect(() => {
ref.current.select();
}, []);

return <input ref={ref} defaultValue="Hello" />
}

新たな非推奨化

非推奨化:element.ref

React 19 では props としての ref がサポートされるため、element.ref を非推奨化します。代わりに element.props.ref を使用します。

element.ref にアクセスすると、以下の警告が表示されます。

Console
Accessing element.ref is no longer supported. ref is now a regular prop. It will be removed from the JSX Element type in a future release.

非推奨化:react-test-renderer

react-test-renderer を非推奨化します。これはユーザが使用する環境とは異なる独自のレンダラ環境を実装しており、内部実装の詳細に対するテストを助長し、React 内部の構造に依存するものだからです。

このテストレンダラは、React Testing Library のようなより実用的なテスト戦略が利用可能になる前に作成されたものです。現在では、モダンなテストライブラリの使用が推奨されます。

React 19 では、react-test-renderer は非推奨警告をログに記録するようになり、また並行レンダーに切り替わりました。モダンかつよりよくサポートされたテスト体験のためには、テストを @testing-library/react または @testing-library/react-native に移行することを推奨します。

注目すべき変更点

StrictMode の変更点

React 19 には、Strict Mode に関するいくつかの修正と改善が含まれています。

開発中に Strict Mode で二重レンダーが発生した際、useMemouseCallback は 1 回目のレンダー時にメモ化された結果を 2 回目のレンダーで再利用します。既に Strict Mode に対応しているコンポーネントでは、挙動に違いを感じることはないはずです。

すべての Strict Mode の挙動と同様、これらの機能は開発中にコンポーネントのバグを積極的に目立たせて、本番環境にリリースされる前に修正できるよう設計されています。例えば、開発環境において Strict Mode は初回マウント時に ref コールバック関数を 2 回呼び出すことで、マウントされたコンポーネントがサスペンスフォールバックに置き換えられたときに何が起こるかをシミュレートします。

UMD ビルドの削除

UMD は過去には、ビルドステップなしで React を読み込むための便利な方法として広く使用されていました。現在では、HTML ドキュメント内でスクリプトとしてモジュールをロードするためのモダンな代替手段があります。テストとリリースプロセスの複雑性を軽減するため、React 19 からは UMD ビルドを生成しなくなります。

script タグで React 19 をロードしたい場合は、esm.sh などの ESM ベースの CDN を使用することを推奨します。

<script type="module">
import React from "https://esm.sh/react@19/?dev"
import ReactDOMClient from "https://esm.sh/react-dom@19/client?dev"
...
</script>

React の内部構造に依存するライブラリはアップグレードできない原因となる

本リリースには、SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED のような内部構造を使用しないようにという私たちのお願いを無視しているライブラリに影響を与える可能性のある React 内部構造の変更が含まれています。これらの変更は React 19 における改善を実現するために必要なものであり、ガイドラインに従っているライブラリには影響を与えません。

私たちのバージョニングポリシーに基づき、このような更新は破壊的変更としてリストされませんし、アップグレード方法に関するドキュメントも提供されません。内部実装に依存するコードを削除することを推奨します。

内部構造を使用する影響を反映するために、SECRET_INTERNALS という接尾辞を以下のように変更します。

_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE

将来的には、React の内部構造へのアクセスをより積極的にブロックすることで使用を抑制し、ユーザのアップグレードの妨げとならないようにする予定です。

TypeScript 関連の変更

非推奨化 TypeScript 型を削除

React 19 で削除された API に基づいて、TypeScript の型を整理しました。削除された型の一部は、より関連性の高いパッケージに移動され、他の型はもはや React の挙動を記述するために必要がなくなりました。

補足

型関連の破壊的変更に関する移行のために、types-react-codemod を公開しました。

npx types-react-codemod@latest preset-19 ./path-to-app

element.props への型安全ではないアクセスを多く行っている場合、以下の追加の codemod を実行できます。

npx types-react-codemod@latest react-element-default-any-props ./path-to-your-react-ts-files

サポートされている書き換えのリストについては、types-react-codemod をご覧ください。codemod が不足していると感じた場合は、React 19 で不足している codemod のリストで追跡できます。

ref クリーンアップの必須化

この変更は、react-19 の codemod プリセットに no-implicit-ref-callback-return として含まれています。

ref クリーンアップ関数の導入により、ref コールバックから何か他のものを返すと、TypeScript によって拒否されるようになります。通常、修正は、暗黙の return の使用をやめることです。

- <div ref={current => (instance = current)} />
+ <div ref={current => {instance = current}} />

元のコードは HTMLDivElement のインスタンスを返していますが、TypeScript はこれがクリーンアップ関数のつもりで書かれたものかどうかを判断できません。

useRef の引数の必須化

この変更は、react-19 のコードモッドプリセットに refobject-defaults として含まれています。

TypeScript と React の動作に関する長年の不満のひとつが useRef でした。今後 useRef には引数が必須になるよう型を変更することにしました。これにより、型シグネチャが大幅に簡素化されます。これで、createContext と同様に動作するようになります。

// @ts-expect-error: Expected 1 argument but saw none
useRef();
// Passes
useRef(undefined);
// @ts-expect-error: Expected 1 argument but saw none
createContext();
// Passes
createContext(undefined);

これにより、すべての ref はミュータブルになります。null で初期化したために ref を変更できない、という問題で困ることはなくなるでしょう。

const ref = useRef<number>(null);

// Cannot assign to 'current' because it is a read-only property
ref.current = 1;

MutableRef は廃止され、useRef は常に単一の RefObject 型を返すようになりました。

interface RefObject<T> {
current: T
}

declare function useRef<T>: RefObject<T>

useRef には便宜上のオーバーロードがまだ存在し、useRef<T>(null) は自動的に RefObject<T | null> を返すようになっています。useRef の引数必須化に対する移行を容易にするために、useRef(undefined) に対する実用的なオーバーロードが追加され、自動的に RefObject<T | undefined> 型を返すようになります。

この変更についてのこれまでの議論は、[RFC] Make all refs mutable をご覧ください。

ReactElement TypeScript 型の変更

この変更は react-element-default-any-props codemod に含まれています。

React 要素が ReactElement として型付けされている場合、その props のデフォルトの型は any ではなく unknown になります。ReactElement に型引数を渡している場合は、影響を受けません。

type Example2 = ReactElement<{ id: string }>["props"];
// ^? { id: string }

しかし、このデフォルトの型に依存していた場合、unknown を扱うようにする必要があります。

type Example = ReactElement["props"];
// ^? Before, was 'any', now 'unknown'

要素の props に対する型安全でないアクセスに依存している古いコードが多くある場合にのみ、これが必要です。要素の内部構造の参照は避難ハッチとしてのみ存在しており、props に安全でないアクセスを行う場合には、any を明示的に用いてそうであると明示するべきです。

TypeScript における JSX 名前空間

この変更は react-19 codemod プリセットに scoped-jsx として含まれています。

グローバルな JSX 名前空間を型から削除して React.JSX に置き換えるというのは長い間の要望でした。これにより、グローバル型の汚染を防ぎ、JSX を利用する異なる UI ライブラリ間での競合が防止できます。

これからは、JSX 名前空間のモジュール拡張 (module augumentation) は declare module "....": でラップする必要があります。

// global.d.ts
+ declare module "react" {
namespace JSX {
interface IntrinsicElements {
"my-element": {
myElementProps: string;
};
}
}
+ }

正確なモジュール指定子は、tsconfig.jsoncompilerOptions で指定した JSX ランタイムに依存します。

  • "jsx": "react-jsx" の場合は react/jsx-runtime です。
  • "jsx": "react-jsxdev" の場合は react/jsx-dev-runtime です。
  • "jsx": "react" および "jsx": "preserve" の場合は react です。

useReducer の型の改善

@mfp22 の協力により、useReducer の型推論が改善されました。

しかしこれには、useReducer がリデューサ全体の型を型パラメータとして受け取る代わりに、型を全く渡さない(型推論に任せる)か、もしくは state の型とアクションの型を両方渡すかのいずれかにする必要がある、という破壊的変更が必要でした。

新しいベストプラクティスは、useReducer に型引数を渡さないことです。

- useReducer<React.Reducer<State, Action>>(reducer)
+ useReducer(reducer)

これは一部の稀なケースでは機能しないかもしれませんが、その場合は state とアクションを明示的に型付けしつつ、Action をタプルで渡すことで解決できます。

- useReducer<React.Reducer<State, Action>>(reducer)
+ useReducer<State, [Action]>(reducer)

リデューサをインラインで定義する場合は、関数パラメータに型注釈を付けることをお勧めします。

- useReducer<React.Reducer<State, Action>>((state, action) => state)
+ useReducer((state: State, action: Action) => state)

これは、useReducer 呼び出しの外にリデューサを移動する場合にも行うべきでしょう。

const reducer = (state: State, action: Action) => state;

Changelog

その他の破壊的変更

  • react-dom: src/href での JavaScript URL に対するエラー #26507
  • react-dom: onRecoverableError から errorInfo.digest を削除 #28222
  • react-dom: unstable_flushControlled を削除 #26397
  • react-dom: unstable_createEventHandle を削除 #28271
  • react-dom: unstable_renderSubtreeIntoContainer を削除 #28271
  • react-dom: unstable_runWithPrioirty を削除 #28271
  • react-is: react-is から非推奨のメソッドを削除 28224

その他の注目すべき変更点

  • react: 同期・デフォルト・連続レーンのバッチ処理 #25700
  • react: サスペンドされたコンポーネントの兄弟を事前レンダーしない #26380
  • react: レンダーフェーズでの更新によって引き起こされる無限更新ループを検出 #26625
  • react-dom: popstate でのトランジションを同期的に #26025
  • react-dom: SSR 中のレイアウトエフェクト警告を削除 #26395
  • react-dom: src/href に空文字列を設定しないよう警告(アンカータグを除く)#28124

React 19 の安定版リリース時に、完全な変更履歴を公開します。


この投稿のレビューと編集に協力してくれた Andrew ClarkEli WhiteJack PopeJan KassensJosh StoryMatt CarrollNoah LemenSophie Alpert、そして Sebastian Silbermann に感謝します。