React v19
2024年12月5日 Reactチーム
React v19がnpmで利用可能になりました!
React 19アップグレードガイドでは、アプリをReact 19にアップグレードするためのステップバイステップの手順を共有しました。この投稿では、React 19の新機能の概要と、それらを導入する方法について説明します。
破壊的な変更のリストについては、アップグレードガイドを参照してください。
React 19の新機能
アクション
Reactアプリの一般的なユースケースは、データ変更を実行し、それに応答して状態を更新することです。たとえば、ユーザーが名前を変更するフォームを送信すると、APIリクエストを行い、その後レスポンスを処理します。これまで、保留中の状態、エラー、楽観的な更新、およびシーケンシャルリクエストを手動で処理する必要がありました。
たとえば、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では、Actionsをベースに、オプティミスティックな更新を管理するためのuseOptimistic
という新しいフックと、Actionsの一般的なケースを処理するための新しいフックReact.useActionState
が導入されました。react-dom
では、フォームを自動的に管理するための<form>
Actionsと、フォームでのActionsの一般的なケースをサポートするための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の新しいAction機能についてそれぞれ詳しく見ていきましょう。
新しいフック: useActionState
Actionsの一般的なケースを簡単にするために、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
は、関数(「Action」)を受け取り、呼び出すためのラップされたActionを返します。これは、Actionsが合成されるためです。ラップされたActionが呼び出されると、useActionState
は、Actionの最後の結果をdata
として、Actionの保留状態をpending
として返します。
詳細については、useActionState
のドキュメントを参照してください。
React DOM: <form>
Actions
Actionsは、react-dom
のReact 19の新しい<form>
機能とも統合されています。<form>
、<input>
、および<button>
要素のaction
およびformAction
プロパティに関数を渡すことで、Actionsでフォームを自動的に送信できるようにサポートを追加しました。
<form action={actionFunction}>
<form>
Actionが成功すると、Reactは自動的に非制御コンポーネントのフォームをリセットします。<form>
を手動でリセットする必要がある場合は、新しいrequestFormReset
React DOM APIを呼び出すことができます。
詳細については、react-dom
ドキュメントの<form>
、<input>
、および<button>
を参照してください。
React DOM: 新しいフック: useFormStatus
デザインシステムでは、コンポーネントにpropsを渡すことなく、コンポーネントが存在する<form>
に関する情報にアクセスする必要があるデザインコンポーネントを作成するのが一般的です。これはContextを通じて行うことができますが、一般的なケースをより簡単にするために、新しいフックuseFormStatus
を追加しました。
import {useFormStatus} from 'react-dom';
function DesignButton() {
const {pending} = useFormStatus();
return <button type="submit" disabled={pending} />
}
useFormStatus
は、フォームがContextプロバイダーであるかのように、親の<form>
のステータスを読み取ります。
詳細については、react-dom
ドキュメントのuseFormStatus
を参照してください。
新しいフック: 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
値に戻ります。
詳細については、useOptimistic
のドキュメントを参照してください。
新しいAPI: use
React 19 では、レンダー内でリソースを読み込むための新しいAPIとして use
を導入します。
例えば、use
を使ってPromiseを読み込むことができます。ReactはPromiseが解決されるまでサスペンドします。
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
を使ってContextを読み込むこともできます。これにより、早期リターンの後など、条件付きでContextを読み込むことが可能になります。
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 DOMの静的API
静的サイト生成のために、react-dom/static
に2つの新しいAPIを追加しました。
これらの新しい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 DOM静的APIを参照してください。
Reactサーバーコンポーネント
サーバーコンポーネント
サーバーコンポーネントは、クライアントアプリケーションやSSRサーバーとは別の環境で、バンドルする前にコンポーネントを事前にレンダリングできる新しいオプションです。この分離された環境がReactサーバーコンポーネントの「サーバー」です。サーバーコンポーネントは、CIサーバーでビルド時に一度実行することも、Webサーバーを使ってリクエストごとに実行することもできます。
React 19には、Canaryチャネルに含まれていたすべてのReactサーバーコンポーネントの機能が含まれています。つまり、サーバーコンポーネントを同梱するライブラリは、react-server
export conditionを使って、フルスタックReactアーキテクチャをサポートするフレームワークで使用するために、React 19をピア依存関係としてターゲットにできるようになりました。
詳細については、Reactサーバーコンポーネントのドキュメントを参照してください。
サーバーアクション
サーバーアクションを使用すると、クライアントコンポーネントはサーバー上で実行される非同期関数を呼び出すことができます。
Server Action が "use server"
ディレクティブで定義されている場合、フレームワークはサーバー関数への参照を自動的に作成し、その参照をクライアントコンポーネントに渡します。クライアントでその関数が呼び出されると、React は関数を実行するためにサーバーにリクエストを送信し、その結果を返します。
Server Actions は Server Components で作成し、props として Client Components に渡すことも、Client Components でインポートして使用することもできます。
詳細については、React Server Actionsのドキュメントを参照してください。
React 19 での改善点
prop としての ref
React 19 から、関数コンポーネントの prop として ref
にアクセスできるようになりました。
function MyInput({placeholder, ref}) {
return <input placeholder={placeholder} ref={ref} />
}
//...
<MyInput ref={ref} />
新しい関数コンポーネントでは forwardRef
が不要になり、新しい ref
prop を使用するようにコンポーネントを自動的に更新する codemod を公開する予定です。今後のバージョンでは、forwardRef
を非推奨にし、削除する予定です。
ハイドレーションエラーの差分
react-dom
のハイドレーションエラーに関するエラー報告も改善しました。たとえば、不一致に関する情報なしに、DEV で複数のエラーをログに記録するのではなく、
不一致の差分を含む単一のメッセージをログに記録するようになりました。
if (typeof window !== 'undefined')
。- Date.now()
や Math.random()
のように、呼び出されるたびに変化する変数入力。- サーバーと一致しないユーザーのロケールでの日付書式設定。- HTML とともにスナップショットを送信せずに外部データが変化した場合。- 無効な HTML タグのネスト。
また、React がロードされる前に HTML を混乱させるブラウザ拡張機能がクライアントにインストールされている場合にも発生する可能性があります。
https://react.dokyumento.jp/link/hydration-mismatch
<App> <span>+ クライアント- サーバー
at throwOnHydrationMismatch ...プロバイダーとしての <Context>
React 19 では、<Context.Provider>
の代わりにプロバイダーとして <Context>
をレンダリングできます。
const ThemeContext = createContext('');
function App({children}) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
新しい Context プロバイダーは <Context>
を使用でき、既存のプロバイダーを変換するための codemod を公開する予定です。今後のバージョンでは、<Context.Provider>
を非推奨にする予定です。
refs のクリーンアップ関数
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 refs、クラスコンポーネントへの refs、および useImperativeHandle
で機能します。
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}) {
// 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では、この複雑さに対処し、クライアントでの同時レンダリングとサーバーでのストリーミングレンダリングへのさらに深い統合を、スタイルシートの組み込みサポートによって実現します。Reactにスタイルシートのprecedence
を伝えると、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
</>
}
スタイルシートを手動でロードすることに慣れているユーザーにとって、これは、依存するコンポーネントの近くにスタイルシートを配置する機会であり、より良いローカルな推論を可能にし、実際に依存するスタイルシートのみをロードすることを容易にします。
スタイルライブラリやバンドラーとのスタイル統合もこの新しい機能を採用できるため、自分でスタイルシートを直接レンダリングしなくても、ツールがこの機能を活用するようにアップグレードされれば恩恵を受けることができます。
詳細については、<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が関連のないハイドレーションの不一致のためにドキュメント全体を再レンダリングする必要がある場合、サードパーティのスクリプトとブラウザ拡張機能によって挿入されたスタイルシートはそのまま残されます。
より良いエラー報告
React 19では、重複を削除し、キャッチされたエラーとキャッチされなかったエラーを処理するためのオプションを提供するために、エラー処理を改善しました。たとえば、エラー境界によってキャッチされたレンダリング中にエラーが発生した場合、以前のReactではエラーが2回スローされ(最初は元のエラーに対して、次に自動回復に失敗した後)、エラーが発生した場所に関する情報とともにconsole.error
が呼び出されていました。
これにより、キャッチされたエラーごとに3つのエラーが発生していました。
React 19では、すべてのエラー情報が含まれた単一のエラーをログに記録します
さらに、onRecoverableError
を補完するために、2つの新しいルートオプションを追加しました。
onCaughtError
: Reactがエラー境界でエラーをキャッチしたときに呼び出されます。onUncaughtError
: エラーがスローされ、エラー境界でキャッチされなかった場合に呼び出されます。onRecoverableError
: エラーがスローされ、自動的に回復した場合に呼び出されます。
詳細と例については、createRoot
とhydrateRoot
のドキュメントを参照してください。
カスタム要素のサポート
React 19では、カスタム要素が完全にサポートされ、Custom Elements Everywhereのすべてのテストに合格しています。
過去のバージョンでは、Reactでカスタム要素を使用することは困難でした。Reactが認識されないpropsをプロパティではなく属性として扱っていたためです。React 19では、クライアント側とSSRの両方で動作するプロパティのサポートが、以下の戦略で追加されました。
- サーバーサイドレンダリング:カスタム要素に渡されるpropsは、その型が
string
、number
のようなプリミティブ値であるか、値がtrue
の場合、属性としてレンダリングされます。object
、symbol
、function
のような非プリミティブ型であるか、値がfalse
のpropsは省略されます。 - クライアントサイドレンダリング:カスタム要素のインスタンス上のプロパティと一致するpropsはプロパティとして割り当てられ、それ以外の場合は属性として割り当てられます。
Reactにおけるカスタム要素のサポートの設計と実装を推進してくれたJoey Arharに感謝します。
アップグレード方法
詳しい手順と、破壊的な変更点や注目すべき変更点の完全なリストについては、React 19 アップグレードガイドをご覧ください。
注:この投稿は元々2024年4月25日に公開されましたが、安定版リリースに伴い2024年12月5日に更新されました。