はじめに
2024年12月5日、React 19が安定版としてリリースされましたね🚀
本記事では公式リリースノートの内容を、DeepL翻訳を活用して日本語に翻訳しました。
より詳細な情報は、ぜひ公式リリースノートをご確認ください。
翻訳元📕は、こちらです。
React 19が正式にリリースされました
- npmで利用可能になりました
- 4月のRC発表から、suspended treesのプリウォーミングとReact DOM static APIが追加されました。
React 19の新機能
Actions
Reactアプリの一般的なユースケースは、データ変異を実行し、それに応答して状態を更新することです。
例えば、ユーザーが名前を変更するためにフォームを送信すると、APIリクエストを行い、レスポンスを処理します。
以前は、保留状態、エラー、楽観的更新、逐次リクエストを手動で処理する必要がありました。
例えば、保留状態やエラー状態をuseState
で処理することができました:
// 以前の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
を使えば、ペンディング状態を処理することができます。
// Actionsからpending状態を利用する
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 の応答性と対話性を維持することができます。
慣習上、非同期遷移を使用する関数は「アクション」と呼ばれます。
アクションが自動的にデータ提出を管理します:
- 保留状態: アクションは、リクエストの開始時に開始し、最終的なステートの更新がコミットされたときに自動的にリセットされる保留状態を提供する。
-
Optimistic アップデート: アクションが新しい
useOptimistic
フックをサポートしたので、リクエストの送信中にユーザーに即座にフィードバックを表示できます。 - エラー処理: リクエストに失敗したときにエラー境界を表示したり、楽観的な更新を自動的に元の値に戻すことができます。
-
フォーム:
<form>
要素はaction
とformAction
プロップに関数を渡せるようになりました。action
props に関数を渡すと、デフォルトで Actions が使われ、送信後にフォームが自動的にリセットされます。
Actionsの上に構築されたReact 19は、楽観的な更新を管理するuseOptimistic
と、Actionsの一般的なケースを処理する新しいフックReact.useActionState
を導入しています。
react-dom
では、フォームを自動的に管理するための<form>
アクションと、フォームでのアクションの一般的なケースをサポートするuseFormStatus
を追加しています。
React 19では、上記の例は次のように単純化できます:
// <form>アクションと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の新しいActionの各機能を説明します。
新しいhook: useActionState
アクションでよくあるケースを簡単にするために、useActionState
という新しいフックが追加されました:
const [error, submitAction, isPending] = useActionState(
async (previousState, newName) => {
const error = await updateName(newName);
if (error) {
// アクションの結果は何でも返すことができます。
// ここでは、エラーだけを返します。
return error;
}
// 成功の処理
return null;
},
null,
);
useActionState
は関数(アクション)を受け取り、呼び出すためのラップされたアクションを返す。これは、Actionが合成されるためです。
ラップされたActionが呼び出されると、useActionState
はActionの最後の結果をdata
として返し、Actionの保留状態をpending
として返します。
React.useActionState
は、CanaryリリースではReactDOM.useFormState
と呼ばれていましたが、名前を変更し、useFormState
を非推奨となりました。
React DOM: <form>
Actions
ActionsはReact 19のreact-dom
の新しいフォーム機能とも統合されています。
<form>
、<input>
、<button>
要素の action
および formAction
propsに関数を渡すことで、Actions を使って自動的にフォームを送信できるようになりました:
<form action={actionFunction}>
<form>
アクションが成功すると、React は制御されていないコンポーネントのフォームを自動的にリセットします。
<form>
を手動でリセットする必要がある場合は、新しい requestFormReset
React DOM API を呼び出すことができます。
React DOM: 新しいhook: useFormStatus
デザインシステムにおいて、コンポーネントに小道具を掘り下げることなく、<form>
内の情報にアクセスする必要があるデザインコンポーネントを書くことはよくあります。
これは Context を使って行うことができますが、よくあるケースをより簡単にするために、新しいフック useFormStatus
を追加しました:
import {useFormStatus} from 'react-dom';
function DesignButton() {
const {pending} = useFormStatus();
return <button type="submit" disabled={pending} />
}
useFormStatus
は親<form>
のステータスを、あたかもそのフォームが Context プロバイダであるかのように読み込みます。
新しいhook: useOptimistic
データ変異を実行するときのもう1つの一般的な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
の値に切り替えます。
新しいAPI: use
React 19では、renderでリソースを読み込むための新しいAPI、use
を導入しています。
例えば、useを使ってプロミスを読み込むと、Reactはプロミスが解決するまでSuspendします:
import {use} from 'react';
function Comments({commentsPromise}) {
// useはプロミスが解決するまで中断します。
const comments = use(commentsPromise);
return comments.map(comment => <p key={comment.id}>{comment}</p>);
}
function Page({commentsPromise}) {
// コメント欄で`use`がサスペンスされた場合、
// このサスペンス・バウンダリーが表示されます。
return (
<Suspense fallback={<div>Loading...</div>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
)
}
use
はレンダーで作成されたプロミスをサポートしません。
renderで作成したpromiseを渡して使おうとすると、Reactは警告を出します。
また、use
でコンテキストを読むこともでき、早期復帰後など条件付きでコンテキストを読むことができます:
import {use} from 'react';
import ThemeContext from './ThemeContext'
function Heading({children}) {
if (children == null) {
return null;
}
// これはuseContextでは早期リターンのため機能しません。
const theme = use(ThemeContext);
return (
<h1 style={{color: theme.color}}>
{children}
</h1>
);
}
use
APIは、フックと同様、レンダー内でのみ呼び出すことができます。フックとは異なり、use
は条件付きで呼び出すことができます。
将来的には、use
を使用してrenderでリソースを消費する多くの方法をサポートする予定だそうです。
新しいReact DOM Static APIs
静的サイト生成のためにreact-dom/static
に2つの新しいAPIが追加されました:
prerender
prerenderToNodeStream
これらの新しいAPIは、静的HTML生成のためにデータのロードを待つことで、renderToString
を改良されています。
これらは、Node.js StreamsやWeb Streamsのようなストリーミング環境で動作するように設計されています。
たとえば、Web Stream 環境では、prerender を使用して React ツリーを静的 HTML にプリレンダリングできます:
import { prerender } from 'react-dom/static';
async function handler(request) {
const {prelude} = await prerender(<App />, {
bootstrapScripts: ['/main.js']
});
return new Response(prelude, {
headers: { 'content-type': 'text/html' },
});
}
プリレンダーAPIは、静的なHTMLストリームを返す前に、すべてのデータがロードされるのを待ちます。
ストリームは文字列に変換することも、ストリーミング応答で送信することもできます。
既存の React DOM サーバー・レンダリング API でサポートされている、コンテンツの読み込み時のストリーミングはサポートされていません。
React Server Components
Server Components
Server Componentsは、クライアントアプリケーションやSSRサーバーとは別の環境で、バンドルする前のコンポーネントを先にレンダリングできる新しいオプションです。
この独立した環境が、React Server Componentsの「サーバー」です。
Server Componentsは、CIサーバー上でビルド時に一度だけ実行することも、Webサーバーを使ってリクエストごとに実行することもできます。
React 19には、Canaryチャネルから含まれるReact Server Componentsのすべての機能が含まれています。
これは、Server Componentsとともに出荷されるライブラリが、フルスタックReactアーキテクチャをサポートするフレームワークで使用するために、react-server
エクスポート条件を持つピア依存関係としてReact 19をターゲットにできるようになったことを意味します。
Server Componentsのサポートを構築するには?
React 19のReact Server Componentsは安定しており、メジャーバージョン間で壊れることはありませんが、React Server Componentsバンドルまたはフレームワークを実装するために使用される基礎となるAPIはsemverに従っておらず、React 19.xのマイナーバージョン間で壊れる可能性があります。
バンドルやフレームワークとして React Server Components をサポートするには、特定の React バージョンに固定するか、Canary リリースを使用することをお勧めされています。React Server Componentsの実装に使用されるAPIを安定させるために、今後もバンドルラーやフレームワークと協力していくそうです。
Server Actions
サーバーアクションは、クライアントコンポーネントがサーバー上で実行される非同期関数を呼び出すことを可能にします。
サーバーアクションが"use server"
ディレクティブで定義されると、フレームワークは自動的にサーバー関数への参照を作成し、その参照をクライアントコンポーネントに渡します。
その関数がクライアントで呼び出されると、Reactはその関数を実行するリクエストをサーバーに送り、結果を返します。
React 19の改良点
propsとしてのref
React 19から、関数コンポーネントのpropとしてref
にアクセスできるようになりました:
function MyInput({placeholder, ref}) {
return <input placeholder={placeholder} ref={ref} />
}
//...
<MyInput ref={ref} />
新しい関数コンポーネントはforwardRef
を必要としなくなり、新しいref
propを使用するようにコンポーネントを自動的に更新するcodemodを公開される予定です。
将来のバージョンでは、forwardRef
は非推奨となり、削除される予定です。
クラスに渡されるrefs
は、コンポーネントのインスタンスを参照するので、propsとして渡されません。
ハイドレーションエラーの差分
また、react-dom
におけるハイドレーションエラーのエラーレポートも改善しました。
例えば、ミスマッチに関する情報を一切持たずにDEVで複数のエラーをログに記録する代わりに:
現在では、ミスマッチの差分を1つのメッセージとして記録されています:
プロバイダーとしての<Context>
React 19では、<Context.Provider>
の代わりに<Context>
をプロバイダとしてレンダリングできます:
const ThemeContext = createContext('');
function App({children}) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
新しい Context プロバイダは <Context>
を使うことができ、既存のプロバイダを変換するための codemod を公開される予定です。
将来のバージョンでは、<Context.Provider>
は廃止される予定です。
参照用クリーンアップ関数
ref
コールバックからクリーンアップ関数を返せるようになりました:
<input
ref={(ref) => {
// refの作成
// NEW: 要素が DOM から削除されたときに ref をリセットするクリーンアップ関数を返します。
return () => {
// refのクリーンアップ
};
}}
/>
コンポーネントがアンマウントされると、Reactはref
コールバックから返されたクリーンアップ関数を呼び出します。
これは、DOM参照、クラスコンポーネントへの参照、useImperativeHandle
で動作します。
以前は、Reactはコンポーネントをアンマウントするときにnull
でref
関数を呼び出していました。
ref
がクリーンアップ関数を返す場合、Reactはこのステップをスキップするようになりました。
将来のバージョンでは、コンポーネントをアンマウントする際にnull
でref
を呼び出すことは非推奨となります。
ref
クリーンアップ関数の導入により、ref
コールバックからそれ以外のものを返すと、TypeScriptによって拒否されるようになりました。
この問題を解決するには、暗黙的なリターンを使わないようにするのが一般的のようです:
- <div ref={current => (instance = current)} />
+ <div ref={current => {instance = current}} />
元のコードはHTMLDivElement
のインスタンスを返していたので、TypeScriptはこれがクリーンアップ関数なのか、クリーンアップ関数を返したくないのかがわかりませんでした。
このパターンは、no-implicit-ref-callback-returnでコード改造できます。
useDeferredValue 初期値
useDeferredValue
にinitialValue
オプションを追加しました:
function Search({deferredValue}) {
// 初期レンダリング時の値は''です。
// その後、延期された値で再レンダリングがスケジュールされます。
const value = useDeferredValue(deferredValue, '');
return (
<Results query={value} />
);
}
initialValue
が提供された場合、useDeferredValue
はコンポーネントの最初のレンダリングのための値としてそれを返し、返されたdeferredValue
でバックグラウンドでの再レンダリングをスケジュールします。
ドキュメント・メタデータのサポート
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、サーバーコンポーネントで確実に動作するようになります。
スタイルシートのサポート
スタイルシートは、外部リンク (<link rel="stylesheet" href="...">
) とインライン (<style>...</style>
) の両方で、スタイルの優先順位の規則により、DOM 内での配置に注意が必要です。
コンポーネント内で合成可能なスタイルシート機能を構築するのは困難であるため、ユーザーは多くの場合、スタイルに依存する可能性のあるコンポーネントから離れた場所ですべてのスタイルをロードするか、この複雑さをカプセル化するスタイル ライブラリを使用することになります。
React 19では、この複雑さに対処し、スタイルシートのビルトイン・サポートを使用して、クライアント上の並行レンダリングとサーバー上のストリーミング・レンダリングへの統合をさらに深くしています。
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 はそのスタイルシートに依存する Suspenseバウンダリのコンテンツを公開する前に、スタイルシートがクライアントの <head>
に挿入されるようにします。
クライアントサイドレンダリングの間、Reactはレンダリングをコミットする前に、新しくレンダリングされたスタイルシートがロードされるのを待ちます。
アプリケーション内の複数の場所からこのコンポーネントをレンダリングする場合、React はスタイルシートをドキュメントに 1 回だけ含めます:
function App() {
return <>
<ComponentOne />
...
<ComponentOne /> // won't lead to a duplicate stylesheet link in the DOM
</>
}
スタイルシートを手動でロードすることに慣れているユーザーにとって、これは、スタイルシートに依存するコンポーネントと一緒にスタイルシートの位置を特定する機会であり、より良いローカル推論を可能にし、実際に依存するスタイルシートのみをロードすることをより簡単にします。
スタイル・ライブラリやバンドラーとのスタイル統合もこの新機能を採用できるため、自分のスタイルシートを直接レンダリングしない場合でも、この機能を使用できるようにツールがアップグレードされれば、恩恵を受けることができます。
非同期スクリプトのサポート
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はスクリプトを1度だけロードして実行します。
サーバーサイドレンダリングでは、非同期スクリプトは<head>
に含まれ、スタイルシート、フォント、画像のプリロードなど、ペイントをブロックするより重要なリソースの後ろに優先されます。
リソースのプリロードをサポート
ドキュメントの初期ロード時やクライアントサイドの更新時に、可能な限り早い段階でロードする必要がありそうなリソースをブラウザに伝えることで、ページのパフォーマンスに劇的な影響を与えることができます。
React 19には、ブラウザ・リソースのロードとプリロードのための新しいAPIが多数含まれており、非効率的なリソース・ロードによって妨げられることのない優れたエクスペリエンスをできるだけ簡単に構築できるようになっています。
import { prefetchDNS, preconnect, preload, preinit } from 'react-dom'
function MyComponent() {
preinit('https://.../path/to/some/script.js', {as: 'script' }) // このスクリプトをロードして熱心に実行する
preload('https://.../path/to/font.woff', { as: 'font' }) // このフォントをプリロードする
preload('https://.../path/to/stylesheet.css', { as: 'style' }) // このスタイルシートをプリロードする
prefetchDNS('https://...') // このホストに実際に何もリクエストしていない場合
preconnect('https://...') // 何かをリクエストしたいが、何をリクエストすればいいかわからないとき
}
<!-- 上記の場合、次のようなDOM/HTMLになります -->
<html>
<head>
<!-- リンク/スクリプトは、呼び出し順ではなく、早期ロードに役立つものから優先されます -->
<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は、フォントのような追加リソースの発見をスタイルシートのロードの外に移動することによって、最初のページロードを最適化するために使用することができます。
また、予期されるナビゲーションで使用されるリソースのリストをプリフェッチし、クリック時やホバー時にもそれらのリソースを熱心にプリロードすることで、クライアントの更新をより速くすることができます。
サードパーティのスクリプトや拡張機能との互換性
サードパーティのスクリプトやブラウザの拡張機能を考慮し、ハイドレーションを改善しました。
ハイドレート時に、クライアントでレンダリングされる要素がサーバーからのHTMLで見つかった要素と一致しない場合、Reactはコンテンツを修正するためにクライアントの再レンダリングを強制します。
以前は、サードパーティのスクリプトやブラウザ拡張機能によって要素が挿入されると、ミスマッチエラーが発生し、クライアントがレンダリングされていました。
React 19では、<head>
と<body>
内の予期しないタグはスキップされ、ミスマッチエラーが回避されます。
Reactが無関係な水和のミスマッチのためにドキュメント全体を再レンダリングする必要がある場合、サードパーティのスクリプトやブラウザ拡張機能によって挿入されたスタイルシートはそのまま残されます。
より良いエラーレポート
React 19のエラー処理を改善し、重複をなくし、キャッチしたエラーとキャッチしていないエラーを処理するオプションを提供しました。
例えば、レンダリング中にエラー境界によって捕捉されたエラーがある場合、以前はReactはエラーを2回投げていました(元のエラーに対して1回、自動回復に失敗した後にもう1回)。
React 19では、すべてのエラー情報を含む単一のエラーログを記録します:
さらに、onRecoverableErrorを補完する2つの新しいルート・オプションを追加しました:
-
onCaughtError
: Reactがエラー境界でエラーをキャッチしたときに呼び出されます。 -
onUncaughtError
: Error Boundaryで捕捉されないエラーがスローされたときに呼び出されます。 -
onRecoverableError
: エラーが発生したときに呼ばれ、自動的に回復する。
カスタム・エレメントのサポート
React 19はカスタム要素を完全にサポートし、Custom Elements Everywhereのすべてのテストに合格しています。
過去のバージョンでは、Reactでカスタム要素を使用することは困難でした。なぜなら、Reactは認識できないpropをプロパティではなく属性として扱うからです。
React 19では、以下のような戦略で、クライアントとSSR中に動作するプロパティのサポートを追加しました:
-
サーバサイドレンダリング:カスタム要素に渡されたpropsは、その型が文字列や数値のようなプリミティブな値であるか、値がtrueである場合、属性としてレンダリングされます。
object
、symbol
、function
、またはvaluefalse
のような非プリミティブ型のpropsは省略されます。 - クライアント側レンダリング:カスタム要素インスタンスのプロパティにマッチする小道具はプロパティとして割り当てられ、そうでない場合は属性として割り当てられます。
最後に
以上が、React 19安定版リリースノートの日本語訳となります。
個人的な感想ですが、Next.jsで先行実装されていたServer Components関連の機能が正式にReactの一部となったことで、今後は他のフレームワークへの展開も期待され、Reactエコシステム全体の進化を加速していきそうですね🥺