React v18.0

2022年3月29日 Reactチーム


React 18が npm で利用可能になりました!前回の記事では、React 18へのアプリのアップグレードに関するステップバイステップの手順を共有しました。今回の記事では、React 18の新機能の概要と、それが将来にとって何を意味するのかについて説明します。


この最新のメジャーバージョンには、自動バッチ処理、startTransitionのような新しいAPI、Suspenseをサポートしたストリーミングサーバーサイドレンダリングなど、すぐに使える改善が含まれています。

React 18の多くの機能は、新しい並行レンダラーの上に構築されており、これは強力な新機能をアンロックする舞台裏の変更です。並行Reactはオプトインです。つまり、並行機能を使用する場合にのみ有効になりますが、人々のアプリケーション構築方法に大きな影響を与えると確信しています。

私たちはReactにおける並行性のサポートを長年研究・開発しており、既存のユーザーに段階的な採用パスを提供するために細心の注意を払ってきました。昨年の夏には、React 18ワーキンググループを結成し、コミュニティの専門家からのフィードバックを集め、Reactエコシステム全体にとってスムーズなアップグレード体験を保証しました。

見逃した方のために、React Conf 2021でこのビジョンの多くを共有しました。

以下に、並行レンダリングから始まるこのリリースで期待できることの完全な概要を示します。

注記

React Nativeユーザーの場合、React 18は、新しいReact NativeアーキテクチャでReact Nativeに出荷されます。詳細については、React Confの基調講演はこちらをご覧ください。

並行Reactとは?

React 18で最も重要な追加機能は、皆さんが決して考える必要がないと願っているものです。それは、並行性です。これは、アプリケーション開発者には概ね当てはまると考えていますが、ライブラリのメンテナにとっては少し複雑な話になるかもしれません。

並行性は、それ自体が機能ではありません。これは、ReactがUIの複数のバージョンを同時に準備できるようにする、舞台裏の新しいメカニズムです。並行性は実装の詳細と考えることができます。これは、それをアンロックする機能のために価値があります。Reactは、優先度付きキューや複数バッファリングのような内部実装で高度な手法を使用しています。しかし、これらの概念が公開APIのどこにも表示されることはありません。

APIを設計する際、実装の詳細を開発者から隠すようにしています。React開発者として、ユーザーエクスペリエンスをどのように見せたいかに焦点を当て、Reactは、そのエクスペリエンスをどのように提供するかを処理します。したがって、React開発者が並行性の仕組みを理解しているとは想定していません。

ただし、並行Reactは、典型的な実装の詳細よりも重要です。これは、Reactのコアレンダリングモデルへの基本的な更新です。そのため、並行性がどのように機能するかを知ることは非常に重要ではありませんが、それが高レベルで何であるかを知る価値があるかもしれません。

並行Reactの重要な特性は、レンダリングが中断可能であることです。React 18に最初にアップグレードするとき、並行機能を追加する前は、更新は以前のバージョンのReactと同じようにレンダリングされます。つまり、単一の中断されない同期トランザクションでレンダリングされます。同期レンダリングでは、更新のレンダリングが開始されると、ユーザーが画面で結果を確認できるまで、何もそれを中断することはできません。

並行レンダリングでは、これは必ずしも当てはまりません。Reactは、更新のレンダリングを開始し、途中で一時停止し、後で再開する可能性があります。場合によっては、進行中のレンダリングを完全に破棄することもあります。Reactは、レンダリングが中断されてもUIが一貫して表示されることを保証します。これを行うために、ツリー全体が評価されたら、最後にDOMの変更を実行するまで待機します。この機能を使用すると、Reactはメインスレッドをブロックせずにバックグラウンドで新しい画面を準備できます。これは、大規模なレンダリングタスクの途中であっても、UIがユーザーの入力に即座に応答し、スムーズなユーザーエクスペリエンスを作成できることを意味します。

もう1つの例は、再利用可能な状態です。並行Reactは、UIのセクションを画面から削除し、以前の状態を再利用しながら後で追加できます。たとえば、ユーザーが画面からタブを切り替えて戻ってきた場合、Reactは以前の画面を以前の状態に復元できる必要があります。今後のマイナーバージョンでは、このパターンを実装する<Offscreen>という新しいコンポーネントを追加する予定です。同様に、ユーザーがそれを表示する前に準備ができるように、Offscreenを使用してバックグラウンドで新しいUIを準備できるようになります。

並行レンダリングはReactの強力な新しいツールであり、新しい機能のほとんどはSuspense、トランジション、ストリーミングサーバーレンダリングなど、それを利用するように構築されています。しかし、React 18は、この新しい基盤の上に構築しようとするもののほんの始まりにすぎません。

並行機能の段階的な採用

技術的には、並行レンダリングは破壊的な変更です。並行レンダリングは中断可能であるため、有効にするとコンポーネントの動作がわずかに異なります。

テストでは、数千のコンポーネントをReact 18にアップグレードしました。私たちが発見したのは、既存のコンポーネントのほとんどすべてが、変更なしで並行レンダリングで「動作する」ということです。ただし、一部のコンポーネントでは、追加の移行作業が必要になる場合があります。変更は通常わずかですが、自分のペースで変更を行うことができます。React 18の新しいレンダリング動作は、新しい機能を使用するアプリの部分でのみ有効になります。

全体的なアップグレード戦略は、既存のコードを壊すことなく、アプリケーションを React 18 で動作させることです。その後、自分のペースで徐々に並行処理機能を追加していくことができます。開発中に並行処理に関連するバグを表面化させるために、<StrictMode> を利用できます。Strict Mode は本番環境での動作には影響しませんが、開発中には追加の警告をログに記録し、冪等であると期待される関数を二重に呼び出します。すべてを捕捉できるわけではありませんが、最も一般的なタイプのミスを防ぐのに効果的です。

React 18 にアップグレードすると、すぐに並行処理機能を使用できるようになります。たとえば、`startTransition` を使用して、ユーザー入力をブロックすることなく画面間を移動できます。または、`useDeferredValue` を使用して、コストのかかる再レンダリングを抑制できます。

ただし、長期的には、アプリに並行処理を追加する主な方法は、並行処理対応のライブラリまたはフレームワークを使用することになると予想されます。ほとんどの場合、並行 API と直接やり取りすることはありません。たとえば、開発者が新しい画面に移動するたびに `startTransition` を呼び出す代わりに、ルーターライブラリがナビゲーションを自動的に `startTransition` でラップします。

ライブラリが並行処理に対応するようにアップグレードするには、時間がかかる場合があります。ライブラリが並行処理機能を活用しやすくするために、新しい API を提供しました。それまでの間、React エコシステムを徐々に移行していくため、メンテナーの方々にはご辛抱をお願いいたします。

詳細については、以前の投稿「React 18 へのアップグレード方法」を参照してください。

データフレームワークにおける Suspense

React 18 では、Relay、Next.js、Hydrogen、Remix などの意見の分かれるフレームワークでデータフェッチングに Suspense の使用を開始できます。Suspense を使用したアドホックなデータフェッチングは技術的には可能ですが、一般的な戦略としてはまだ推奨されていません。

将来的には、意見の分かれるフレームワークを使用せずに、Suspense を使用してデータにアクセスしやすくする追加のプリミティブを公開する可能性があります。ただし、Suspense は、アプリケーションのアーキテクチャ、つまり、ルーター、データレイヤー、サーバーレンダリング環境に深く統合されている場合に最適に機能します。そのため、長期的には、ライブラリとフレームワークが React エコシステムで重要な役割を果たすと予想されます。

以前のバージョンの React と同様に、React.lazy を使用してクライアントでコード分割に Suspense を使用することもできます。しかし、Suspense に対する私たちのビジョンは、常にコードのロード以上のものを目指してきました。目標は、最終的に、同じ宣言的な Suspense フォールバックが、あらゆる非同期操作 (コード、データ、画像などのロード) を処理できるように、Suspense のサポートを拡張することです。

Server Components は開発中です

Server Components は、サーバーとクライアントにまたがるアプリを構築できるようにする、今後の機能です。クライアントサイドアプリの豊富なインタラクティビティと、従来のサーバーレンダリングのパフォーマンス向上を組み合わせることができます。Server Components は、本質的に Concurrent React と結合しているわけではありませんが、Suspense やストリーミングサーバーレンダリングなどの並行処理機能と最も相性が良いように設計されています。

Server Components はまだ実験的な段階ですが、マイナーな 18.x リリースで初期バージョンをリリースする予定です。それまでの間、Next.js、Hydrogen、Remix などのフレームワークと協力して、提案を進め、幅広い採用に向けて準備を進めています。

React 18 の新機能

新機能: 自動バッチ処理

バッチ処理とは、React がパフォーマンス向上のために複数の状態更新を単一の再レンダリングにグループ化することです。自動バッチ処理がない場合、React イベントハンドラー内の更新のみをバッチ処理していました。promise、`setTimeout`、ネイティブイベントハンドラー、またはその他のイベント内の更新は、デフォルトでは React でバッチ処理されませんでした。自動バッチ処理を使用すると、これらの更新は自動的にバッチ処理されます。

// Before: only React events were batched.
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will render twice, once for each state update (no batching)
}, 1000);

// After: updates inside of timeouts, promises,
// native event handlers or any other event are batched.
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}, 1000);

詳細については、この投稿「React 18 でのレンダリング回数を減らすための自動バッチ処理」を参照してください。

新機能: トランジション

トランジションは、React における新しい概念で、緊急性の高い更新と緊急性の低い更新を区別するためのものです。

  • 緊急性の高い更新は、タイピング、クリック、押下などの直接的なインタラクションを反映します。
  • トランジション更新は、UI をあるビューから別のビューに遷移させます。

タイピング、クリック、押下などの緊急性の高い更新は、物理オブジェクトの動作に対する直感に一致するように、即座に応答する必要があります。そうしないと、「間違っている」と感じてしまいます。しかし、トランジションは異なります。ユーザーは画面上のすべての中間値を表示することを期待しないためです。

たとえば、ドロップダウンでフィルターを選択する場合、クリックするとフィルターボタン自体がすぐに反応することを期待します。ただし、実際の結果は個別に遷移する場合があります。少しの遅延は知覚できないことが多く、しばしば予期されます。また、結果のレンダリングが完了する前にフィルターを再度変更すると、最新の結果のみが表示されればよいのです。

通常、最高のユーザーエクスペリエンスを得るには、単一のユーザー入力によって、緊急性の高い更新と緊急性の低い更新の両方が発生する必要があります。入力イベント内で `startTransition` API を使用して、どの更新が緊急で、どれが「トランジション」であるかを React に通知できます。

import { startTransition } from 'react';

// Urgent: Show what was typed
setInputValue(input);

// Mark any state updates inside as transitions
startTransition(() => {
// Transition: Show the results
setSearchQuery(input);
});

`startTransition` でラップされた更新は緊急性の低いものとして処理され、クリックやキー押下などのより緊急性の高い更新が発生すると中断されます。トランジションがユーザーによって中断された場合 (たとえば、複数の文字を続けて入力した場合)、React は完了しなかった古いレンダリング作業を破棄し、最新の更新のみをレンダリングします。

  • useTransition: 保留中の状態を追跡するための値を含め、トランジションを開始するためのフック。
  • startTransition: フックを使用できない場合にトランジションを開始するメソッド。

トランジションは並行レンダリングにオプトインするため、更新を中断できます。コンテンツが再サスペンドした場合、トランジションは、トランジションコンテンツをバックグラウンドでレンダリングしている間、React に現在のコンテンツの表示を継続するよう指示します (詳細については、Suspense RFC を参照してください)。

トランジションに関するドキュメントはこちらをご覧ください.

Suspense の新機能

Suspense を使用すると、コンポーネントツリーの一部がまだ表示する準備ができていない場合に、そのローディング状態を宣言的に指定できます。

<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>

Suspense は、React プログラミングモデルで「UI ローディング状態」を第一級の宣言的な概念にします。これにより、その上に高レベルの機能を構築できます。

数年前に、Suspense の限定版を導入しました。ただし、サポートされているユースケースは、React.lazy を使用したコード分割のみであり、サーバーでのレンダリング時にはまったくサポートされていませんでした。

React 18 では、サーバーでの Suspense のサポートを追加し、並行レンダリング機能を使用して機能を拡張しました。

React 18 の Suspense は、トランジション API と組み合わせると最適に機能します。トランジション中にサスペンドすると、React はすでに表示されているコンテンツがフォールバックによって置き換えられないようにします。代わりに、React は悪いローディング状態を防ぐのに十分なデータがロードされるまでレンダリングを遅らせます。

詳細については、React 18 における Suspense の RFC を参照してください。

新しいクライアントおよびサーバーレンダリング API

このリリースでは、クライアントとサーバーでのレンダリング用に公開する API を再設計する機会を得ました。これらの変更により、ユーザーは React 18 の新しい API にアップグレードしながら、React 17 モードで古い API を引き続き使用できます。

React DOM クライアント

これらの新しい API は、react-dom/client からエクスポートされるようになりました

  • createRoot: render または unmount のためのルートを作成する新しいメソッドです。ReactDOM.render の代わりに使用します。React 18 の新機能は、これがないと動作しません。
  • hydrateRoot: サーバーでレンダリングされたアプリケーションをハイドレートする新しいメソッドです。新しい React DOM Server API と組み合わせて、ReactDOM.hydrate の代わりに使用します。React 18 の新機能は、これがないと動作しません。

createRoothydrateRoot は両方とも、レンダリング中またはハイドレーション中に React がエラーから回復した場合に通知を受けたい場合に使用できる onRecoverableError という新しいオプションを受け入れます。デフォルトでは、React は reportError または古いブラウザでは console.error を使用します。

React DOM クライアントのドキュメントはこちらを参照してください.

React DOM サーバー

これらの新しい API は、react-dom/server からエクスポートされるようになり、サーバーでのストリーミング Suspense を完全にサポートしています。

  • renderToPipeableStream: Node 環境でのストリーミング用。
  • renderToReadableStream: Deno や Cloudflare Worker などの最新のエッジランタイム環境用。

既存の renderToString メソッドは引き続き動作しますが、推奨されません。

React DOM サーバーのドキュメントはこちらを参照してください.

新しい厳格モードの動作

将来的には、React が状態を保持しながら UI のセクションを追加および削除できる機能を追加したいと考えています。たとえば、ユーザーが画面からタブを切り替えて戻ったときに、React は前の画面をすぐに表示できる必要があります。これを行うために、React は以前と同じコンポーネントの状態を使用してツリーをアンマウントおよび再マウントします。

この機能により、React アプリはすぐに優れたパフォーマンスを発揮できるようになりますが、コンポーネントはエフェクトが複数回マウントおよび破棄されることに耐性がある必要があります。ほとんどのエフェクトは変更なしで動作しますが、一部のエフェクトは 1 回のみマウントまたは破棄されることを前提としています。

これらの問題を表面化するために、React 18 では厳格モードに開発専用の新しいチェックが導入されています。この新しいチェックは、コンポーネントが初めてマウントされるたびにすべてのコンポーネントを自動的にアンマウントおよび再マウントし、2 回目のマウントで以前の状態を復元します。

この変更前は、React はコンポーネントをマウントしてエフェクトを作成していました

* React mounts the component.
* Layout effects are created.
* Effects are created.

React 18 の厳格モードでは、React は開発モードでコンポーネントのアンマウントと再マウントをシミュレートします

* React mounts the component.
* Layout effects are created.
* 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 effects are created.
* Effects are created.

再利用可能な状態を確保するためのドキュメントはこちらを参照してください.

新しいフック

useId

useId は、ハイドレーションのミスマッチを回避しながら、クライアントとサーバーの両方で一意の ID を生成するための新しいフックです。これは、一意の ID を必要とするアクセシビリティ API と統合するコンポーネントライブラリに主に役立ちます。これは React 17 以前にすでに存在していた問題を解決するものですが、新しいストリーミングサーバーレンダラーが HTML を順不同で配信するため、React 18 ではさらに重要になっています。ドキュメントはこちらを参照してください

注記

useId は、リスト内のキーを生成するためのものではありません。キーはデータから生成する必要があります。

useTransition

useTransitionstartTransition を使うと、いくつかの状態更新を緊急ではないものとしてマークできます。その他の状態更新は、デフォルトで緊急であるとみなされます。Reactは、緊急の状態更新(例えば、テキスト入力の更新)が、緊急ではない状態更新(例えば、検索結果のリストのレンダリング)を中断できるようにします。ドキュメントはこちらを参照してください。

useDeferredValue

useDeferredValue を使うと、ツリーの緊急ではない部分の再レンダリングを遅延させることができます。これはデバウンスに似ていますが、デバウンスと比較していくつかの利点があります。固定された時間遅延がないため、Reactは最初のレンダリングが画面に反映された直後に遅延レンダリングを試みます。遅延レンダリングは中断可能で、ユーザー入力をブロックしません。ドキュメントはこちらを参照してください。

useSyncExternalStore

useSyncExternalStore は、外部ストアへの更新を強制的に同期させることで、外部ストアが同時読み取りをサポートできるようにする新しいフックです。これにより、外部データソースへのサブスクリプションを実装する際に useEffect が不要になり、React の外部にある状態と統合するライブラリに推奨されます。ドキュメントはこちらを参照してください。

注記

useSyncExternalStore は、アプリケーションコードではなく、ライブラリで使用されることを意図しています。

useInsertionEffect

useInsertionEffect は、CSS-in-JS ライブラリがレンダリング時にスタイルを注入する際のパフォーマンス問題を解決できるようにする新しいフックです。CSS-in-JS ライブラリをすでに構築している場合を除き、これを使用することは想定されていません。このフックは、DOMが変更された後、ただしレイアウトエフェクトが新しいレイアウトを読み取る前に実行されます。これはReact 17以前にも存在していた問題を解決するものですが、React 18では、Reactが同時レンダリング中にブラウザに処理を譲り、レイアウトを再計算する機会を与えるため、さらに重要になります。ドキュメントはこちらを参照してください。

注記

useInsertionEffect は、アプリケーションコードではなく、ライブラリで使用されることを意図しています。

アップグレード方法

ステップごとの手順と、破壊的変更および注目すべき変更の完全なリストについては、React 18へのアップグレード方法を参照してください。

変更履歴

React

  • 緊急の更新とトランジションを分離するために、useTransitionuseDeferredValue を追加します。(#10426#10715#15593#15272#15578#15769#17058#18796#19121#19703#19719#19724#20672#20976 by @acdlite@lunaruan@rickhanlonii、および @sebmarkbage)
  • 一意なIDを生成するためにuseIdを追加しました。(#17322, #18576, #22644, #22672, #21260 by @acdlite, @lunaruan, および @sebmarkbage)
  • 外部ストアライブラリがReactと統合するのを支援するために、useSyncExternalStoreを追加しました。(#15022, #18000, #18771, #22211, #22292, #22239, #22347, #23150 by @acdlite, @bvaughn, および @drarmstr)
  • 保留中のフィードバックなしにuseTransitionのバージョンとしてstartTransitionを追加しました。(#19696 by @rickhanlonii
  • CSS-in-JSライブラリ用のuseInsertionEffectを追加しました。(#21913 by @rickhanlonii
  • コンテンツが再表示されるときにSuspenseがレイアウトエフェクトを再マウントするようにしました。(#19322, #19374, #19523, #20625, #21079 by @acdlite, @bvaughn, および @lunaruan)
  • 復元可能な状態をチェックするために、<StrictMode> がエフェクトを再実行するようにしました。(#19523 , #21418 by @bvaughn および @lunaruan
  • Symbolが常に利用可能であると仮定するようにしました。(#23348 by @sebmarkbage
  • object-assignポリフィルを削除しました。(#23351 by @sebmarkbage
  • サポートされていないunstable_changedBits APIを削除しました。(#20953 by @acdlite
  • コンポーネントがundefinedをレンダリングできるようにしました。(#21869 by @rickhanlonii
  • クリックなどの離散イベントに起因するuseEffectを同期的にフラッシュするようにしました。(#21150 by @acdlite
  • Suspenseのfallback={undefined}nullと同じように動作し、無視されないようにしました。(#21854 by @rickhanlonii
  • 同じコンポーネントに解決するすべてのlazy()を同等とみなすようにしました。(#20357 by @sebmarkbage
  • 最初のレンダリング中にコンソールをパッチしないようにしました。(#22308 by @lunaruan
  • メモリ使用量を改善しました。(#21039 by @bgirard
  • 文字列強制変換が例外をスローする場合(Temporal.*、Symbolなど)のメッセージを改善しました。(#22064 by @justingrant
  • MessageChannelよりも、利用可能な場合はsetImmediateを使用するようにしました。(#20834 by @gaearon
  • 中断されたツリー内でコンテキストが伝播に失敗する問題を修正しました。(#23095 by @gaearon
  • 早期のbailoutメカニズムを削除することで、useReducerが間違ったpropsを観測する問題を修正しました。(#22445 by @josephsavona
  • iframeを追加する際にSafariでsetStateが無視される問題を修正しました。(#23111 by @gaearon
  • ツリー内でZonedDateTimeをレンダリングする際のクラッシュを修正しました。(#20617 by @dimaqq
  • テストでdocumentがnullに設定されている場合に発生するクラッシュを修正しました。(#22695 by @SimenB
  • 同時実行機能がオンの場合にonLoadがトリガーされない問題を修正しました。(#23316 by @gnoff
  • セレクターがNaNを返す場合の警告を修正しました。(#23333 by @hachibeeDI
  • テストでdocumentがnullに設定されている場合に発生するクラッシュを修正しました。(#22695 by @SimenB
  • 生成されるライセンスヘッダーを修正しました。(#23004 by @vitaliemiron
  • package.jsonをエントリーポイントの一つとして追加しました。(#22954 by @Jack
  • Suspense境界の外側での中断を許可しました。(#23267 by @acdlite
  • ハイドレーションが失敗するたびに回復可能なエラーをログに記録するようにしました。(#23319 by @acdlite

React DOM

React DOM Server

React DOM Test Utils

  • 本番環境で act が使用された場合にエラーを投げるようにしました。(#21686 by @acdlite
  • global.IS_REACT_ACT_ENVIRONMENT で、誤った act 警告を無効にするのをサポートしました。(#22561 by @acdlite
  • React の作業をスケジュールする可能性のあるすべての API を網羅するように act 警告を拡張しました。(#22607 by @acdlite
  • act をバッチ更新するようにしました。(#21797 by @acdlite
  • 宙ぶらりんのパッシブエフェクトに対する警告を削除しました。(#22609 by @acdlite

React Refresh

  • Fast Refresh で後からマウントされたルートを追跡するようにしました。(#22740 by @anc95
  • package.jsonexportsフィールドを追加します。(#23087 by @otakustay

Server Components (実験的)

  • Server Contextのサポートを追加します。(#23244 by @salazarm
  • lazyのサポートを追加します。(#24068 by @gnoff
  • webpack 5用のwebpackプラグインを更新します(#22739 by @michenly
  • Nodeローダーの誤りを修正します。(#22537 by @btea
  • エッジ環境でwindowの代わりにglobalThisを使用します。(#22777 by @huozhi