React 18へのアップグレード方法
2022年3月8日 Rick Hanlon 著
リリース記事で共有したように、React 18では、既存のアプリケーションへの段階的な導入戦略とともに、新しいコンカレントレンダラーを搭載した機能が導入されています。この記事では、React 18へのアップグレード手順について説明します。
React 18へのアップグレード中に問題が発生した場合は、問題を報告してください。
インストール
Reactの最新バージョンをインストールするには
npm install react react-dom
または、yarnを使用している場合
yarn add react react-dom
クライアントレンダリングAPIの更新
React 18を最初にインストールすると、コンソールに警告が表示されます
React 18では、ルートを管理するためのより優れたエルゴノミクスを提供する新しいルートAPIが導入されています。新しいルートAPIは、新しいコンカレントレンダラーも有効にし、コンカレント機能をオプトインできます。
// Before
import { render } from 'react-dom';
const container = document.getElementById('app');
render(<App tab="home" />, container);
// After
import { createRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = createRoot(container); // createRoot(container!) if you use TypeScript
root.render(<App tab="home" />);
unmountComponentAtNode
をroot.unmount
に変更しました
// Before
unmountComponentAtNode(container);
// After
root.unmount();
また、Suspenseを使用した場合に通常は期待どおりの結果にならないため、renderからコールバックを削除しました
// Before
const container = document.getElementById('app');
render(<App tab="home" />, container, () => {
console.log('rendered');
});
// After
function AppWithCallbackAfterRender() {
useEffect(() => {
console.log('rendered');
});
return <App tab="home" />
}
const container = document.getElementById('app');
const root = createRoot(container);
root.render(<AppWithCallbackAfterRender />);
最後に、アプリがハイドレーションを使用してサーバーサイドレンダリングを使用している場合は、hydrate
をhydrateRoot
にアップグレードしてください
// Before
import { hydrate } from 'react-dom';
const container = document.getElementById('app');
hydrate(<App tab="home" />, container);
// After
import { hydrateRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = hydrateRoot(container, <App tab="home" />);
// Unlike with createRoot, you don't need a separate root.render() call here.
詳細については、こちらでワーキンググループのディスカッションをご覧ください。
サーバーレンダリングAPIの更新
このリリースでは、サーバーでのSuspenseとStreaming SSRを完全にサポートするために、react-dom/server
APIを刷新しています。これらの変更の一環として、サーバーでのインクリメンタルなSuspenseストリーミングをサポートしない古いNodeストリーミングAPIは非推奨になりました。
このAPIを使用すると、警告が表示されるようになりました
renderToNodeStream
:非推奨⛔️
代わりに、Node環境でのストリーミングには、以下を使用します
renderToPipeableStream
:新規✨
また、DenoやCloudflareワーカーなどの最新のエッジランタイム環境でのSuspenseによるストリーミングSSRをサポートする新しいAPIも導入しています
renderToReadableStream
:新規✨
次のAPIは引き続き機能しますが、Suspenseのサポートは制限されます
renderToString
:制限あり⚠️renderToStaticMarkup
:制限あり⚠️
最後に、このAPIは引き続きメールのレンダリングに使用できます
renderToStaticNodeStream
サーバーレンダリング API の変更に関する詳細については、ワーキンググループの投稿「サーバー上での React 18 へのアップグレード」、新しい Suspense SSR アーキテクチャに関する詳細な解説「新しい Suspense SSR アーキテクチャに関する詳細な解説」、そして React Conf 2021 での Shaundai Person 氏の講演「Suspense を用いたストリーミングサーバーレンダリング」を参照してください。
TypeScript 定義の更新
プロジェクトで TypeScript を使用している場合は、@types/react
および @types/react-dom
の依存関係を最新バージョンに更新する必要があります。新しい型はより安全で、型チェッカーで無視されていた問題をキャッチします。最も注目すべき変更点は、props を定義する際に children
prop を明示的にリストする必要があることです。例:
interface MyButtonProps {
color: string;
children?: React.ReactNode;
}
型のみの変更の完全なリストについては、「React 18 型付けプルリクエスト」を参照してください。このプルリクエストには、ライブラリの型における修正例へのリンクが含まれており、コードの調整方法を確認できます。自動移行スクリプトを使用すると、アプリケーションコードを新しいより安全な型付けに迅速に移植できます。
型付けにバグを発見した場合は、DefinitelyTyped リポジトリで問題を提出してください。
自動バッチ処理
React 18 では、デフォルトでより多くのバッチ処理を行うことで、すぐに使えるパフォーマンスの向上が追加されます。バッチ処理とは、React がパフォーマンス向上のために複数の状態更新を単一の再レンダリングにグループ化することです。React 18 より前は、React イベントハンドラー内でのみ更新をバッチ処理していました。Promise、setTimeout、ネイティブイベントハンドラー、またはその他のイベント内の更新は、デフォルトでは React でバッチ処理されませんでした。
// Before React 18 only React events were batched
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will render twice, once for each state update (no batching)
}, 1000);
React 18 で createRoot
を使用すると、すべての更新は、どこから発生したかに関係なく自動的にバッチ処理されます。これは、タイムアウト、Promise、ネイティブイベントハンドラー、またはその他のイベント内の更新が、React イベント内の更新と同じ方法でバッチ処理されることを意味します。
// After React 18 updates inside of timeouts, promises,
// native event handlers or any other event are batched.
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}, 1000);
これは破壊的な変更ですが、レンダリングの作業が減り、アプリケーションのパフォーマンスが向上すると予想されます。自動バッチ処理をオプトアウトするには、flushSync
を使用できます。
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
});
// React has updated the DOM by now
flushSync(() => {
setFlag(f => !f);
});
// React has updated the DOM by now
}
詳細については、「自動バッチ処理の詳細」を参照してください。
ライブラリ用の新しい API
React 18 ワーキンググループでは、スタイルや外部ストアなど、特定のユースケースで同時レンダリングをサポートするために必要な新しい API を作成するために、ライブラリメンテナーと協力しました。React 18 をサポートするために、一部のライブラリでは、以下の API のいずれかに切り替える必要がある場合があります。
useSyncExternalStore
は、ストアの更新を強制的に同期させることで、外部ストアが同時読み取りをサポートできるようにする新しいフックです。この新しい API は、React の外部の状態と統合するすべてのライブラリに推奨されます。詳細については、「useSyncExternalStore の概要に関する投稿」および「useSyncExternalStore API の詳細」を参照してください。useInsertionEffect
は、CSS-in-JS ライブラリがレンダリング時のスタイルの挿入によるパフォーマンスの問題に対処できるようにする新しいフックです。CSS-in-JS ライブラリを既に構築している場合を除き、これを使用することはまずないでしょう。このフックは、DOM が変更された後、レイアウトエフェクトが新しいレイアウトを読み取る前に実行されます。これは React 17 以前から存在していた問題ですが、React 18 では、React が同時レンダリング中にブラウザに処理を譲り、レイアウトを再計算する機会を与えるため、さらに重要になります。詳細については、「<style>
のライブラリアップグレードガイド」を参照してください。
また、React 18 では、startTransition
、useDeferredValue
、useId
などの同時レンダリング用の新しい API も導入されています。これらの詳細については、リリース投稿で詳しく説明しています。
Strict Mode の更新
将来的には、React が状態を保持したまま UI のセクションを追加および削除できるようにする機能を追加したいと考えています。たとえば、ユーザーが画面を切り替えて戻ったときに、React は前の画面をすぐに表示できるようにする必要があります。これを行うために、React は以前と同じコンポーネント状態を使用してツリーをアンマウントおよび再マウントします。
この機能により、React はすぐに使えるパフォーマンスが向上しますが、エフェクトが複数回マウントおよび破棄されることにコンポーネントが対応している必要があります。ほとんどのエフェクトは変更なしで動作しますが、一部のエフェクトは 1 回だけマウントまたは破棄されると想定しています。
これらの問題を明らかにするために、React 18 では、開発専用の新しいチェックが Strict Mode に導入されました。この新しいチェックでは、コンポーネントが最初にマウントされるたびにすべてのコンポーネントを自動的にアンマウントおよび再マウントし、2 回目のマウントで前の状態を復元します。
この変更の前は、React はコンポーネントをマウントし、エフェクトを作成していました
* React mounts the component.
* Layout effects are created.
* Effect effects are created.
React 18 の Strict Mode では、React は開発モードでコンポーネントのアンマウントと再マウントをシミュレートします
* React mounts the component.
* Layout effects are created.
* Effect effects are created.
* React simulates unmounting the component.
* Layout effects are destroyed.
* Effects are destroyed.
* React simulates mounting the component with the previous state.
* Layout effect setup code runs
* Effect setup code runs
詳細については、「StrictMode への再利用可能な状態の追加」および「エフェクトで再利用可能な状態をサポートする方法」のワーキンググループの投稿を参照してください。
テスト環境の構成
createRoot
を使用するようにテストを初めて更新すると、テストコンソールに次の警告が表示される場合があります。
これを修正するには、テストを実行する前に、globalThis.IS_REACT_ACT_ENVIRONMENT
を true
に設定します。
// In your test setup file
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
このフラグの目的は、React に、ユニットテストのような環境で実行されていることを伝えることです。React は、更新を act
でラップするのを忘れた場合に、役立つ警告をログに記録します。
また、フラグを false
に設定して、act
が必要ないことを React に伝えることもできます。これは、完全なブラウザー環境をシミュレートするエンドツーエンド テストに役立ちます。
最終的には、テストライブラリがこれを自動的に構成してくれると期待しています。たとえば、React Testing Library の次のバージョンでは、追加の構成なしで React 18 が組み込みでサポートされています。
act
テスト API と関連する変更の詳細は、ワーキンググループで確認できます。
Internet Explorer のサポート終了
このリリースでは、React は Internet Explorer のサポートを終了します。これは 2022 年 6 月 15 日にサポートが終了します。React 18 で導入された新機能は、IE で適切にポリフィルできないマイクロタスクなどの最新のブラウザー機能を使用して構築されているため、今回この変更を行います。
Internet Explorer をサポートする必要がある場合は、React 17 を使用することをお勧めします。
非推奨
react-dom
:ReactDOM.render
は非推奨になりました。これを使用すると、警告が表示され、アプリは React 17 モードで実行されます。react-dom
:ReactDOM.hydrate
は非推奨になりました。これを使用すると、警告が表示され、アプリは React 17 モードで実行されます。react-dom
:ReactDOM.unmountComponentAtNode
は非推奨になりました。react-dom
:ReactDOM.renderSubtreeIntoContainer
は非推奨になりました。react-dom/server
:ReactDOMServer.renderToNodeStream
は非推奨になりました。
その他の破壊的な変更
- 一貫性のある useEffect のタイミング: クリックやキーダウン イベントなどの個別ユーザー入力イベント中に更新がトリガーされた場合、React はエフェクト関数を常に同期的にフラッシュするようになりました。以前は、この動作は必ずしも予測可能または一貫性のあるものではありませんでした。
- より厳格なハイドレーション エラー: 欠落または余分なテキストコンテンツによるハイドレーションの不一致は、警告ではなくエラーとして扱われるようになりました。React は、サーバーマークアップに一致させようとしてクライアント上のノードを挿入または削除して個々のノードを「修正」しようとしなくなり、ツリー内の最も近い
<Suspense>
バウンダリまでクライアントレンダリングに戻ります。これにより、ハイドレーションされたツリーの一貫性が確保され、ハイドレーションの不一致によって引き起こされる可能性のあるプライバシーとセキュリティの抜け穴を回避できます。 - Suspense ツリーは常に一貫性があります: コンポーネントがツリーに完全に追加される前にサスペンドした場合、React は不完全な状態でツリーに追加したり、そのエフェクトを起動したりしません。代わりに、React は新しいツリーを完全に破棄し、非同期操作が完了するのを待ってから、最初からレンダリングを再試行します。React は再試行を同時並行で、ブラウザをブロックせずにレンダリングします。
- Suspense を使用したレイアウトエフェクト: ツリーが再度サスペンドしてフォールバックに戻ると、React はレイアウトエフェクトをクリーンアップし、バウンダリ内のコンテンツが再び表示されたときにそれらを再作成するようになりました。これにより、Suspense で使用した場合にコンポーネントライブラリがレイアウトを正しく測定できなかった問題が修正されます。
- 新しい JS 環境要件: React は、
Promise
、Symbol
、およびObject.assign
を含む最新のブラウザ機能に依存するようになりました。Internet Explorer など、最新のブラウザ機能をネイティブに提供していない、または非準拠の実装を使用している古いブラウザやデバイスをサポートする場合は、バンドルされたアプリケーションにグローバルなポリフィルを含めることを検討してください。
その他の注目すべき変更
React
- コンポーネントが
undefined
をレンダーできるようになりました: コンポーネントからundefined
を返した場合、Reactは警告を表示しなくなりました。これにより、コンポーネントの戻り値として許可される値が、コンポーネントツリーの途中で許可される値と一貫するようになりました。JSXの前にreturn
文を書き忘れるなどのミスを防ぐために、リンターを使用することをお勧めします。 - テストでは、
act
の警告はオプトインになりました: エンドツーエンドテストを実行している場合、act
の警告は不要です。有用で有益なユニットテストでのみ有効にできるように、オプトインメカニズムを導入しました。 - アンマウントされたコンポーネントでの
setState
に関する警告を削除: これまで、ReactはアンマウントされたコンポーネントでsetState
を呼び出すと、メモリリークに関する警告を表示していました。この警告はサブスクリプションのために追加されましたが、実際にはステートの設定が問題ないシナリオで発生することが多く、回避策によってコードが悪化していました。この警告を削除しました。 - コンソールログの抑制を廃止: Strict Modeを使用すると、Reactは予期しない副作用を見つけるために各コンポーネントを2回レンダリングします。React 17では、ログを読みやすくするために、2回のレンダリングのうち1回のコンソールログを抑制していました。これが混乱を招くというコミュニティからのフィードバックを受けて、抑制を削除しました。代わりに、React DevToolsがインストールされている場合、2回目のログのレンダリングはグレーで表示され、完全に抑制するオプション(デフォルトではオフ)があります。
- メモリ使用量の改善: Reactはアンマウント時に内部フィールドをより多くクリーンアップするようになり、アプリケーションコードに存在する可能性のある未修正のメモリリークによる影響が軽減されました。
React DOM Server
renderToString
: サーバーでサスペンドした場合にエラーを発生させなくなりました。代わりに、最も近い<Suspense>
境界のフォールバックHTMLを出力し、クライアントで同じコンテンツのレンダリングを再試行します。代わりに、renderToPipeableStream
やrenderToReadableStream
のようなストリーミングAPIに切り替えることをお勧めします。renderToStaticMarkup
: サーバーでサスペンドした場合にエラーを発生させなくなりました。代わりに、最も近い<Suspense>
境界のフォールバックHTMLを出力します。
変更履歴
完全な変更履歴はこちらで確認できます。