React 19が来る
6 min read
React19 Betaがnpmで利用可能になったようです。
Actions
慣習で非同期遷移(async transitions)を使用する関数は "Actions" と呼ばれるとのこと。 Actionはデータの送信を自動的に管理してくれる。
- 保留状態
- 楽観的更新
- エラー処理
- フォーム
useTransition()
よくある保留状態とかエラー状態を useState() で処理しているコードがある。
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");
};
これがReact19ではtransitionで非同期関数を使って、保留中のステート、エラー、フォーム、楽観的な更新を自動的に処理できるようになる。 上記のコードを useTransition() を使うと以下のようにpending状態を処理できる。
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");
})
};
非同期トランジション(async transition)はすぐにisPendingをtrueに設定し、非同期リクエストを行い、transition後にisPendingをfalseに切り替える。 フレームワーク側で制御してくれるのは何となく安心感があるかも。
New hook: useActionState
Actionsでよくあるケースを簡単にするために、useActionStateという新しいフックを追加された。
const [error, submitAction, isPending] = useActionState(
async (previousState, newName) => {
const error = await updateName(newName);
if (error) {
// Actionの結果は何でも返すことができる。
// ここではエラーだけを返す。
return error;
}
// 正常系
return null;
},
null,
);
// 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.useActionStateは、CanaryリリースではReactDOM.useFormStateだったが、名前を変更し、useFormStateを非推奨としたとのこと。 useActionStateの詳細についてはドキュメントを参照。
React DOM: form Actions
ActionsはReact 19のreact-domの新しい<form>
機能とも統合されている。
<form>
, <input>
, <button>
要素のactionとformAction propsに関数を渡すことで、Actionを使って自動的にフォームを送信できるようになった。
<form action={actionFunction}>
<form>
Actionが成功すると、Reactは制御されていないコンポーネントのフォームを自動的にリセット。<form>
を手動でリセットする必要がある場合は、requestFormReset React DOM API
を呼び出すことができる。
詳細は <form>
、<input>
、<button>
のドキュメントを参照。
React DOM: New hook: useFormStatus
デザインシステムにおいて、デザインコンポーネントを書くときに、そのコンポーネントの中にある <form>
に関する情報にアクセスする必要がある。これはContextを使って行うことができるが、よくあるケースを簡単にするために、新しいhookであるuseFormStatus
が追加された。
import {useFormStatus} from 'react-dom';
function DesignButton() {
const { pending } = useFormStatus();
return <button type="submit" disabled={pending} />
}
useFormStatusは親の<form>
のステータスをContextのProvierかのように読み込んでくれるらしい!
便利そう。
詳細はドキュメントで。
New hook: useOptimistic
データミューテーションを実行するときのもう1つの一般的なUIパターンは、非同期リクエストの実行中に最終状態を楽観的に表示すること。React19ではこれを簡単にするためにuseOptimisticという新しいhookが追加されている。
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の値に切り替える。 詳細はドキュメント。
New API: use
React 19では、レンダリングでリソースを読み込むための新しいAPIを導入された。 useを使ってプロミスを読み込むと、Reactはプロミスが解決するまでSuspendする。
import {use} from 'react'; // <--
function Comments({commentsPromise}) {
// useはPromiseが解決するまでsuspendする。
const comments = use(commentsPromise); // <--
return comments.map(comment => <p key={comment.id}>{comment}</p>);
}
function Page({commentsPromise}) {
// Commentsで`use`がサスペンドすると、このSuspense boundaryが表示される
return (
<Suspense fallback={<div>Loading...</div>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
)
}
useはrenderで作成されたプロミスをサポートしていない。renderで作成されたプロミスをuseに渡そうとすると、Reactは警告を発します
ここがいまいちわかってない。
Contextも読むことができるとのこと。 ドキュメント
React Server Components
Server Components
Server Componentsは、クライアントアプリケーションやSSRサーバーとは別の環境で、バンドルする前のコンポーネントを先にレンダリングできる新しいオプション。Server Componentsは、CIサーバー上でビルド時に一度だけ実行することも、Webサーバーを使ってリクエストごとに実行することも可能。
Server Actions
クライアントコンポーネントがサーバー上で実行される非同期関数を呼び出すことを可能にする。 server actionsが"use server"ディレクティブで定義されると、フレームワークは自動的にサーバー関数への参照を作成し、その参照をクライアントコンポーネントに渡します。その関数がクライアントで呼び出されると、Reactはサーバにリクエストを送信して関数を実行し、結果を返します。 これもNextjsのapp routerで先に使ってた。便利。
よくある誤解として、Server Componentsは "use server "で示されますが、Server Components用のディレクティブはありません。use server" ディレクティブは、サーバーアクションに使用されます。
React19の改善点(気になったところ抜粋)
ref as a prop
forwardRef
を使わなくても良いようになった!
function MyInput({placeholder, ref}) {
return <input placeholder={placeholder} ref={ref} />
}
//...
<MyInput ref={ref} />
<Context>
as a provider
React 19では、<Context.Provider>
の代わりに<Context>
をプロバイダとしてレンダリングできる。将来的に<Context.Provider>
は廃止されるとのこと。
const ThemeContext = createContext('');
function App({children}) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
useDeferredValue initial value
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} />
);
}
Support for Document Metadata
React 19では、コンポーネント内のドキュメントメタデータタグをネイティブにレンダリングするためのサポートを追加されるとのこと。react-helmetとか使わなくても良くなる!ただreact-helmetのようなライブラリが必要なケースもあるかも。
Support for stylesheets
コンポーネントごとにlinkの形式でスタイルシートを呼び出せるようになった?
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>
)
}
Support for async scripts
同様にscriptのレンダリングもできるようになったぽい。
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>
}