React 19 RC
April 25, 2024 by The React Team
npm で React 19 Beta が利用可能になりました!
React 19 RC アップグレードガイドでは、アプリを React 19 にアップグレードするためのステップバイステップガイドをお示ししました。この投稿では、React 19 の新機能と、それらをどのように採用するかについて概説します。
破壊的変更のリストについては、アップグレードガイドを参照してください。
React 19 の新機能
アクション (Action)
React アプリの一般的なユースケースは、データの書き換えを行い、それに応じて state を更新するというものです。例えば、ユーザがフォームを送信してユーザ名を変更する場合、API リクエストを発行し、そのレスポンスを処理します。これまでは、送信中 (pending) 状態、エラー、楽観的更新やリクエストの順序について、手動で管理する必要がありました。
例えば、送信中状態やエラーの処理は、以下のように useState
を使って行っていました。
// Before Actions
function UpdateName({}) {
const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, setIsPending] = useState(false);
const handleSubmit = async () => {
setIsPending(true);
const error = await updateName(name);
setIsPending(false);
if (error) {
setError(error);
return;
}
redirect("/path");
};
return (
<div>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
}
React 19 では、トランジション内で非同期関数を使用することで、送信中状態、エラー、フォーム、楽観的更新を自動的に処理するためのサポートが追加されます。
例えば、useTransition
を使用すれば以下のように送信中状態を処理できます。
// Using pending state from Actions
function UpdateName({}) {
const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, startTransition] = useTransition();
const handleSubmit = () => {
startTransition(async () => {
const error = await updateName(name);
if (error) {
setError(error);
return;
}
redirect("/path");
})
};
return (
<div>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
}
この非同期トランジションは、開始直後に isPending
状態を true にセットし、非同期リクエストを実行し、すべてのトランジション終了後に isPending
を false に切り替えます。これにより、データが変更されている最中も、現在の UI をレスポンシブかつインタラクティブに保つことができます。
アクションの仕組みを土台として、React 19 では楽観的更新を管理するための useOptimistic
と、アクションの一般的なユースケースを扱うための新しいフックである React.useActionState
が導入されます。react-dom
では、フォームを自動的に管理する <form>
アクションと、フォームにおけるアクションの一般的なユースケースをサポートする useFormStatus
が追加されています。
React 19 では、上記の例は以下のように簡略化できます。
// Using <form> Actions and useActionState
function ChangeName({ name, setName }) {
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const error = await updateName(formData.get("name"));
if (error) {
return error;
}
redirect("/path");
return null;
},
null,
);
return (
<form action={submitAction}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>Update</button>
{error && <p>{error}</p>}
</form>
);
}
以下のセクションで、React 19 の新しいアクション関連機能を詳しく説明していきます。
新しいフック:useActionState
アクションの一般的なユースケースを簡単に実現するため、useActionState
という新しいフックを追加しました。
const [error, submitAction, isPending] = useActionState(
async (previousState, newName) => {
const error = await updateName(newName);
if (error) {
// You can return any result of the action.
// Here, we return only the error.
return error;
}
// handle success
return null;
},
null,
);
useActionState
は関数(「アクション」本体)を受け取り、そのアクションをラップしたものを返します。これが動作するのはアクションのコンポジションが可能だからです。ラップされたアクションが呼び出されると、useActionState
はアクションの最終結果を data
として、アクションの進行中状態を pending
として返します。
詳細は、useActionState
のドキュメントをご覧ください。
React DOM:<form>
アクション
アクションはまた、react-dom
における React 19 の新しい <form>
機能とも統合されています。<form>
、<input>
、<button>
の各要素において、props である action
および formAction
に関数を渡すことがサポートされるようになりました。これによりアクションを用いて自動的にフォームの送信が可能です。
<form action={actionFunction}>
<form>
アクションが成功すると、React は非制御コンポーネントの場合にフォームを自動的にリセットします。手動で <form>
をリセットする必要がある場合は、新しい React DOM の API である requestFormReset
を呼び出すことができます。
詳細は、react-dom
のドキュメントで <form>
、<input>
、<button>
をご覧ください。
React DOM の新しいフック:useFormStatus
デザインシステムにおいてはよく、props を深く受け渡すことなしに、自身の所属する <form>
に関する情報にアクセスする必要があるデザインコンポーネントを書くことがあります。これはコンテクストを介して行うことも可能ですが、一般的なユースケースを簡単に行えるよう、新しいフックである useFormStatus
を追加しました。
import {useFormStatus} from 'react-dom';
function DesignButton() {
const {pending} = useFormStatus();
return <button type="submit" disabled={pending} />
}
useFormStatus
を使うことで、まるで親フォーム自体がコンテクストプロバイダであるかのように、親 <form>
の状態を読み取れます。
詳細は、react-dom
ドキュメントの useFormStatus
をご覧ください。
新しいフック:useOptimistic
データの書き換えを行う際に一般的な UI パターンは、非同期リクエストの進行中に、最終的にとるはずの状態を先に楽観的に表示しておくというものです。React 19 では、これを簡単に行うための新しいフックである useOptimistic
を追加しています。
function ChangeName({currentName, onUpdateName}) {
const [optimisticName, setOptimisticName] = useOptimistic(currentName);
const submitAction = async formData => {
const newName = formData.get("name");
setOptimisticName(newName);
const updatedName = await updateName(newName);
onUpdateName(updatedName);
};
return (
<form action={submitAction}>
<p>Your name is: {optimisticName}</p>
<p>
<label>Change Name:</label>
<input
type="text"
name="name"
disabled={currentName !== optimisticName}
/>
</p>
</form>
);
}
useOptimistic
フックは、updateName
リクエストが進行中になると、optimisticName
を即座にレンダーします。更新が完了するかエラーが発生すると、React は自動的に currentName
の値に戻します。
詳細については、useOptimistic
のドキュメントをご覧ください。
新しい API:use
React 19 では、レンダー中にリソースを読み取るための新しい API である use
を導入しています。
たとえば、use
でプロミスを読み取ることができます。プロミスが解決 (resolve) するまで React はサスペンドします。
import {use} from 'react';
function Comments({commentsPromise}) {
// `use` will suspend until the promise resolves.
const comments = use(commentsPromise);
return comments.map(comment => <p key={comment.id}>{comment}</p>);
}
function Page({commentsPromise}) {
// When `use` suspends in Comments,
// this Suspense boundary will be shown.
return (
<Suspense fallback={<div>Loading...</div>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
)
}
また、use
を使用してコンテクストを読み取ることもでき、早期リターンの後などに条件付きでコンテクストを読み取れるようになります。
import {use} from 'react';
import ThemeContext from './ThemeContext'
function Heading({children}) {
if (children == null) {
return null;
}
// This would not work with useContext
// because of the early return.
const theme = use(ThemeContext);
return (
<h1 style={{color: theme.color}}>
{children}
</h1>
);
}
use
API は、フックと同様にレンダー中にのみ呼び出すことができます。しかしフックとは異なり、use
は条件分岐の中でも呼び出すことが可能です。将来的には、use
を使用してレンダー中にリソースを使用する方法をさらに拡充する予定です。
詳細については、use
のドキュメントをご覧ください。
React Server Components
サーバコンポーネント
サーバコンポーネントは、クライアントアプリケーションや SSR サーバとは別の環境で、バンドル前にコンポーネントを事前レンダーするための新しいオプションです。React Server Components の “server” とはこの別の環境を指しています。サーバコンポーネントは、CI サーバでビルド時に一度だけ実行することも、ウェブサーバを使用してリクエストごとに実行することもできます。
React 19 には、Canary チャンネルにあったすべての React Server Components の機能が含まれています。これにより、サーバコンポーネントを使用するライブラリは、React 19 を peer dependency としてターゲットにすることができ、react-server
エクスポート条件 を用いてフルスタック React アーキテクチャをサポートするフレームワークで使用できます。
詳細については、React Server Components のドキュメントをご覧ください。
サーバアクション
サーバアクションにより、クライアントコンポーネントからサーバ上で実行される非同期関数を呼び出せるようになります。
サーバアクションが "use server"
ディレクティブを用いて定義されると、フレームワークは自動的にサーバ関数への参照を作成し、その参照をクライアントコンポーネントに渡します。クライアントでその関数が呼び出されると、React はサーバにリクエストを送り、関数を実行し、その結果を返します。
サーバアクションをサーバコンポーネント内で作成し props としてクライアントコンポーネントに渡すことも、クライアントコンポーネントでインポートして使用することもできます。
詳細については、React サーバアクションのドキュメントをご覧ください。
React 19 の改善点
ref
が props に
React 19 から、関数コンポーネントにおいて ref
に props としてアクセスできるようになりました。
function MyInput({placeholder, ref}) {
return <input placeholder={placeholder} ref={ref} />
}
//...
<MyInput ref={ref} />
新しい関数コンポーネントでは forwardRef
が不要になります。新しい props である ref
を使用するようコンポーネントを自動的に更新する codemod を公開する予定です。将来のバージョンでは forwardRef
は非推奨となり、削除されます。
ハイドレーションエラー時の差分表示
react-dom
におけるハイドレーションエラーのエラーレポートを改善しました。これまで開発時には、たとえば以下のようなエラーが複数ログとして記録されていましたが、ハイドレーション不一致に関する情報は含まれていませんでした。
今後は、不一致部分の差分を含んだ単一のメッセージがログに記録されるようになります。
if (typeof window !== 'undefined')
.
- Variable input such as Date.now()
or Math.random()
which changes each time it’s called.
- Date formatting in a user’s locale which doesn’t match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.
It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.
https://react.dev/link/hydration-mismatch
<App>
<span>
+ Client
- Server
at throwOnHydrationMismatch
…<Context>
がプロバイダに
React 19 では、<Context.Provider>
の代わりに <Context>
をプロバイダとしてレンダーできます。
const ThemeContext = createContext('');
function App({children}) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
新しいコンテクストプロバイダは <Context>
のように使用できるため、既存のプロバイダを変換するための codemod を公開する予定です。将来のバージョンでは <Context.Provider>
を非推奨にする予定です。
ref 用のクリーンアップ関数
ref
コールバックからクリーンアップ関数を返すことがサポートされるようになりました。
<input
ref={(ref) => {
// ref created
// NEW: return a cleanup function to reset
// the ref when element is removed from DOM.
return () => {
// ref cleanup
};
}}
/>
コンポーネントがアンマウントされると、React は ref
コールバックから返されたクリーンアップ関数を呼び出します。これは DOM への ref、クラスコンポーネントへの ref、および useImperativeHandle
のいずれに対しても機能します。
ref クリーンアップ関数の導入により、ref
コールバックからそれ以外のものを返すことは TypeScript によって拒否されるようになりました。通常、これは暗黙の return をやめることで修正できます。例えば以下のようにします。
- <div ref={current => (instance = current)} />
+ <div ref={current => {instance = current}} />
元のコードは HTMLDivElement
のインスタンスを返していますが、TypeScript はこれがクリーンアップ関数を返すつもりでミスをしたのか、クリーンアップ関数を返したくないのか判断できないのです。
このパターンは no-implicit-ref-callback-return
の codemod を用いて修正できます。
useDeferredValue
の初期値
useDeferredValue
に initialValue
オプションを追加しました。
function Search({deferredValue}) {
// On initial render the value is ''.
// Then a re-render is scheduled with the deferredValue.
const value = useDeferredValue(deferredValue, '');
return (
<Results query={value} />
);
}
initialValue が指定された場合、useDeferredValue
はコンポーネントの初期レンダーの際にそれを value
として返し、バックグラウンドで deferredValue を返すための再レンダーをスケジュールします。
詳細については、useDeferredValue
をご覧ください。
ドキュメントメタデータのサポート
HTML では、<title>
、<link>
、<meta>
などのドキュメントメタデータタグは、ドキュメントの <head>
セクションに配置されるよう決まっています。しかし React では、アプリに必要なメタデータを決めるコンポーネントが <head>
をレンダーしている場所から非常に遠いことや、そもそも React が <head>
をレンダーしないことがあります。過去には、これらの要素をエフェクトで手動で挿入するか react-helmet
のようなライブラリを使用しつつ、React アプリケーションをサーバでレンダーする際に特別な注意を払う必要がありました。
React 19 では、コンポーネントでドキュメントメタデータタグをレンダーするネイティブのサポートを追加しました。
function BlogPost({post}) {
return (
<article>
<h1>{post.title}</h1>
<title>{post.title}</title>
<meta name="author" content="Josh" />
<link rel="author" href="https://twitter.com/joshcstory/" />
<meta name="keywords" content={post.keywords} />
<p>
Eee equals em-see-squared...
</p>
</article>
);
}
React がこのコンポーネントをレンダーする際、<title>
、<link>
、<meta>
タグを認識し、自動的にドキュメントの <head>
セクションに移動させます。これらのメタデータタグをネイティブにサポートすることで、クライアントのみのアプリ、ストリーミング SSR、サーバコンポーネントのいずれでも動作することを保証できます。
詳細については <title>
、<link>
、<meta>
のドキュメントをご覧ください。
スタイルシートのサポート
スタイルシートには優先順位に関するルールがあるため、外部リンク (<link rel="stylesheet" href="...">
) される場合でもインライン (<style>...</style>
) の場合でも、DOM 内での配置を慎重に考える必要があります。コンポーネントのコンポジション能力を保ちつつ、コンポーネント内でスタイルシート機能を構築することは困難です。そのためユーザはよく、スタイルに依存するコンポーネントから遠く離れた場所でスタイルをまとめてロードするか、この複雑さをカプセル化するスタイルライブラリを使用する必要がありました。
React 19 では、スタイルシートに対する組み込みのサポートを提供することでこの複雑さに対処し、またクライアントにおける並行レンダー機能やサーバにおけるストリーミングレンダー機能との深い統合を行います。スタイルシートの precedence
(優先度)を React に伝えることで、スタイルシートの DOM への挿入順序を管理し、また外部スタイルシートの場合はそれがロードされてからそのスタイルルールに依存するコンテンツが表示されるよう保証します。
function ComponentOne() {
return (
<Suspense fallback="loading...">
<link rel="stylesheet" href="foo" precedence="default" />
<link rel="stylesheet" href="bar" precedence="high" />
<article class="foo-class bar-class">
{...}
</article>
</Suspense>
)
}
function ComponentTwo() {
return (
<div>
<p>{...}</p>
<link rel="stylesheet" href="baz" precedence="default" /> <-- will be inserted between foo & bar
</div>
)
}
サーバサイドレンダリングの場合、React は <head>
にスタイルシートを含めることで、それをブラウザがロードするまで描画が起きないことを保証します。ストリーミングを既に開始した後でスタイルシートが見つかった場合でも、React はサスペンスバウンダリ内でスタイルシートに依存するコンテンツが表示される前に、クライアントの <head>
にそのスタイルシートが挿入されることを保証します。
クライアントサイドレンダリングの場合、React は新しくレンダーされたスタイルシートが読み込まれるのを待ってからレンダーをコミットします。そのコンポーネントをアプリケーション内の複数の場所からレンダーする場合でも、React はドキュメントにスタイルシートを一度だけ挿入します。
function App() {
return <>
<ComponentOne />
...
<ComponentOne /> // won't lead to a duplicate stylesheet link in the DOM
</>
}
スタイルシートを手動で読み込むことに慣れているユーザにとっては、これはスタイルシートをそれに依存するコンポーネントの隣に配置することで局所的な理解をしやすくできる機会となり、また実際に使用するスタイルシートのみが読み込まれることを保証しやすくなるでしょう。
スタイル関連ライブラリやバンドラ統合のスタイル機能もこの新しい機能を採用できるため、自分でスタイルシートを直接レンダーしない場合でも、ツールがこの機能を使用するようにアップグレードされるにつれ、やはりメリットを享受できるようになるでしょう。
詳細については、<link>
と <style>
のドキュメントを参照してください。
非同期スクリプトのサポート
HTML では通常のスクリプト (<script src="...">
) と遅延スクリプト (<script defer="" src="...">
) はドキュメントの順序で読み込まれるため、コンポーネントツリーの深い部分でこれらのスクリプトをレンダーすることは困難です。しかし、非同期スクリプト (<script async="" src="...">
) は任意の順序で読み込まれます。
React 19 では、非同期スクリプトのサポートを拡充し、ツリーのどこにあっても、スクリプトを実際に使用するコンポーネント内でそれをレンダーできるようにします。これにより、スクリプトインスタンスの移動や重複解消処理の管理が不要になります。
function MyComponent() {
return (
<div>
<script async={true} src="..." />
Hello World
</div>
)
}
function App() {
<html>
<body>
<MyComponent>
...
<MyComponent> // won't lead to duplicate script in the DOM
</body>
</html>
}
すべてのレンダー環境で非同期スクリプトの重複解消処理が行われます。複数の異なるコンポーネントが同じスクリプトをレンダーしている場合でも、React はそれを一度だけ読み込み、実行します。
サーバサイドレンダリングでは、非同期スクリプトは <head>
に挿入され、描画をブロックするスタイルシート・フォント・画像プリロードなどのより重要なリソースよりも低優先度で処理されます。
詳細については、<script>
のドキュメントを参照してください。
リソースのプリロードのサポート
ドキュメントの初期読み込み時やクライアントでの画面更新時に、今後必要になりそうなリソースについてブラウザに可能な限り早く知らせておくことは、ページのパフォーマンスに劇的な影響を与えることがあります。
React 19 には、リソースの読み込みやプリロードのための新しい API が多数含まれています。非効率的なリソース読み込みによって阻害されることのない素晴らしいユーザ体験を、できるだけ容易に構築できるようになっています。
import { prefetchDNS, preconnect, preload, preinit } from 'react-dom'
function MyComponent() {
preinit('https://.../path/to/some/script.js', {as: 'script' }) // loads and executes this script eagerly
preload('https://.../path/to/font.woff', { as: 'font' }) // preloads this font
preload('https://.../path/to/stylesheet.css', { as: 'style' }) // preloads this stylesheet
prefetchDNS('https://...') // when you may not actually request anything from this host
preconnect('https://...') // when you will request something but aren't sure what
}
<!-- the above would result in the following DOM/HTML -->
<html>
<head>
<!-- links/scripts are prioritized by their utility to early loading, not call order -->
<link rel="prefetch-dns" href="https://...">
<link rel="preconnect" href="https://...">
<link rel="preload" as="font" href="https://.../path/to/font.woff">
<link rel="preload" as="style" href="https://.../path/to/stylesheet.css">
<script async="" src="https://.../path/to/some/script.js"></script>
</head>
<body>
...
</body>
</html>
これらの API は、フォントなどのリソースをスタイルシート外に移動させて早期に発見できるようにすることで、ページの初期ロードを最適化するために利用できます。また、予想されるナビゲーションに応じて必要となるリソースのリストを先読みし、クリックあるいはホバー時に早期にプリロードを始めることで、クライアントでの更新を高速化することもできるでしょう。
詳細については、リソースプリロード API を参照してください。
サードパーティのスクリプトおよび拡張機能との互換性改善
サードパーティのスクリプトやブラウザ拡張機能の存在を考慮し、ハイドレーションの改善を行いました。
ハイドレーション時に、クライアントでレンダーされた要素がサーバから届いた HTML に書かれた要素と一致しない場合、React は強制的にクライアントで再レンダーを行って内容を修正します。これまで、サードパーティのスクリプトやブラウザ拡張機能によって何らかの要素が挿入された場合、不一致エラーとクライアントレンダーを引き起こしていました。
React 19 では、<head>
および <body>
内にある予期しないタグをスキップし、不一致エラーが回避されるようになります。それ以外のハイドレーションの不一致によりドキュメント全体を再レンダーする必要がある場合でも、サードパーティのスクリプトやブラウザ拡張機能によって挿入された既存のスタイルシートはそのままにします。
エラー報告の改善
React 19 ではエラー処理を改善して重複エラーを削減しており、またキャッチされたエラーとキャッチされなかったエラーのそれぞれに対する処理オプションを提供します。たとえば、レンダー中にエラーが発生しエラーバウンダリでキャッチされた場合、これまで React ではそのエラーを 2 回スロー(1 回は元のエラー、もう 1 回は自動回復に失敗した後)し、その後エラーが発生した場所について console.error
に記録していました。
これにより、キャッチされるエラーごとに以下のような 3 つのエラーが発生していました。
React 19 では、すべてのエラー情報を含む単一のエラーをログに記録します。
さらに、onRecoverableError
を補完するために 2 つの新しいルートオプションを追加しました。
onCaughtError
:エラーバウンダリで React がエラーをキャッチしたときに呼び出されます。onUncaughtError
:エラーがスローされたがエラーバウンダリでキャッチされなかった場合に呼び出されます。onRecoverableError
:エラーがスローされ、自動的に回復されたときに呼び出されます。
詳細と例については、createRoot
および hydrateRoot
のドキュメントを参照してください。
カスタム要素のサポート
React 19 はカスタム要素を完全にサポートし、Custom Elements Everywhere のすべてのテストに合格しました。
過去のバージョンの React では、認識されない props を要素のプロパティではなく属性として扱っていたため、カスタム要素を使用することが困難でした。React 19 では、以下の戦略に従って、クライアントと SSR の両方で機能するプロパティがサポートされるようになります。
- サーバサイドレンダリング:カスタム要素に渡される props は、その型が
string
、number
のようなプリミティブである場合や値がtrue
である場合、属性としてレンダーされる。object
、symbol
、function
のような非プリミティブ値である場合や値がfalse
である場合、その props は無視される。 - クライアントサイドレンダリング:カスタム要素のインスタンスのプロパティに一致する props はプロパティとして割り当てられ、それ以外の場合は属性として割り当てられる。
React におけるカスタム要素のサポートに関し、設計と実装を推進した Joey Arhar に感謝します。
アップグレード方法
アップグレードに関するステップバイステップのガイドや、重要な変更点の完全なリストについては、React 19 アップグレードガイドを参照してください。