React 19 RC アップグレードガイド
April 25, 2024 by Ricky Hanlon
React 19 に追加された改善にはいくつかの破壊的変更が必要ですが、アップグレードをできるだけスムーズに行えるよう努力しているため、ほとんどのアプリには影響が出ないことを予想しています。
この投稿では、React 19 にアップグレードする手順をご案内します。
React 19 をテストしていただける方は、このアップグレードガイドに従い、遭遇した問題を報告してください。React 19 に追加された新機能のリストについては、React 19 リリースのお知らせをご覧ください。
インストール
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 のサポートもより良好です。
変更のうち codemod が利用できるものは以下で紹介されています。
利用可能なすべての codemod の一覧については、react-codemod
リポジトリを参照してください。
破壊的変更
レンダー中のエラーは再スローされない
これまでのバージョンの React では、レンダー中にスローされたエラーはキャッチされた後に再スローされていました。開発環境では、console.error
にもログを出力していたため、エラーログの重複が発生していました。
React 19 では、重複を減らすためにエラーの扱いを改善し、再スローは行わないようになりました。
- キャッチされないエラー:エラーバウンダリによってキャッチされないエラーは
window.reportError
に報告されます。 - キャッチされたエラー:エラーバウンダリによってキャッチされたエラーは
console.error
に報告されます。
この変更はほとんどのアプリに影響を与えないはずですが、本番環境におけるエラーレポートシステムが再スローの挙動に依存している場合は、エラー処理を更新する必要があります。これをサポートするために、カスタムのエラー処理を行うための新しい手段を createRoot
と hydrateRoot
に追加しました。
const root = createRoot(container, {
onUncaughtError: (error, errorInfo) => {
// ... log error report
},
onCaughtError: (error, errorInfo) => {
// ... log error report
}
});
詳細は、createRoot
と hydrateRoot
のドキュメントを参照してください。
非推奨化 React API の削除
廃止:関数コンポーネントの propTypes
と defaultProps
PropTypes
は 2017 年 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>;
}
廃止:contextTypes
と getChildContext
を使用したレガシーコンテクスト
レガシーコンテクストは 2018 年 10 月 (v16.6.0) に非推奨化されました。
レガシーコンテクストは、contextTypes
と getChildContext
を使用するクラスコンポーネントでのみ利用可能であり、見逃しやすい微妙なバグのため、のちに 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} />;
}
}
削除:モジュールパターンファクトリ
モジュールパターンファクトリは 2019 年 8 月 (v16.9.0) に非推奨化されました。
このパターンはほとんど使用されておらず、サポートすることで React がわずかに大きく、遅くなる原因となっています。React 19 ではモジュールパターンファクトリのサポートが削除されており、通常の関数に移行する必要があります。
// Before
function FactoryComponent() {
return { render() { return <div />; } }
}
// After
function FactoryComponent() {
return <div />;
}
削除:React.createFactory
createFactory
は 2020 年 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 DOM API の削除
削除:react-dom/test-utils
act
を react-dom/test-utils
から react
パッケージに移動しました。
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.この警告を修正するには、act
を react
からインポートするようにします。
- import {act} from 'react-dom/test-utils'
+ import {act} from 'react';
その他の test-utils
関数はすべて削除されました。これらのユーティリティは一般的ではなく、コンポーネントや React の低レベルな内部実装の詳細へ依存しやすくなってしまうものでした。React 19 でこれらの関数を呼び出すとエラーとなります。将来のバージョンではエクスポートが削除されます。
代替手段については、警告ページをご覧ください。
削除:ReactDOM.render
ReactDOM.render
は 2022 年 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 />);
削除:ReactDOM.hydrate
ReactDOM.hydrate
は 2022 年 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.unmountComponentAtNode
は 2022 年 3 月 (v18.0.0) に非推奨化されました。React 19 では、root.unmount()
を使用するよう移行する必要があります。
// Before
unmountComponentAtNode(document.getElementById('root'));
// After
root.unmount();
詳細については、createRoot
と hydrateRoot
の root.unmount()
をご覧ください。
削除:ReactDOM.findDOMNode
ReactDOM.findDOMNode
は 2018 年 10 月 (v16.6.0) に非推奨化されました。
findDOMNode
はレガシーな避難ハッチであり、実行速度が遅く、リファクタリングが困難で、最初の子要素しか返せず、抽象化レイヤーを破壊するといった問題があるため(詳細はこちら)、削除されます。ReactDOM.findDOMNode
は DOM 用の 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
にアクセスすると、以下の警告が表示されます。
非推奨化: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 で二重レンダーが発生した際、useMemo
と useCallback
は 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
をご覧ください。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.json
の compilerOptions
で指定した 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 Clark、Eli White、Jack Pope、Jan Kassens、Josh Story、Matt Carroll、Noah Lemen、Sophie Alpert、そして Sebastian Silbermann に感謝します。