React 19 実践:Concurrent 機能・Actions・新 Hooks
React 19 でアップグレードする価値
React 19 はこれまで実験的だった並行(Concurrent)機能を安定 API としてまとめ上げ、長年の弱点だった「フォーム / データ変更」を補完しました。核となる考え方は一文で言えます:書き込みは Action で包み、読み取りは並行プリミティブで包むことで、待っている間も UI が固まらないようにする。
| 機能 | React 18 | React 19 |
|---|---|---|
| Server Components | 実験的(Next.js 等) | ドキュメント級の安定機能に |
| フォーム送信 | 手書き useState + onSubmit |
useActionState / <form action> |
| Promise / Context の読み取り | use は不可 |
use() でレンダー中に読み取り可能 |
| ref | useRef + forwardRef のみ |
Props として直接渡せる |
| 楽観的更新 | 手書きの状態切り替え | useOptimistic |
| 並行トランジション | useTransition |
useTransition + 強化された useDeferredValue |
Actions:書き込みをファーストクラスに
Action とは「React が待機できる」関数です。<form action> や useActionState と組み合わせると、React が pending 状態を自動管理し、送信ボタンを無効化します。
"use client";
import { useActionState } from "react";
async function subscribe(prevState: { ok: boolean; msg: string }, formData: FormData) {
const email = formData.get("email");
const res = await fetch("/api/subscribe", {
method: "POST",
body: JSON.stringify({ email }),
});
if (!res.ok) return { ok: false, msg: "登録に失敗しました" };
return { ok: true, msg: "登録完了!" };
}
export function SubscribeForm() {
const [state, formAction, pending] = useActionState(subscribe, {
ok: false,
msg: "",
});
return (
<form action={formAction}>
<input name="email" type="email" required />
<button disabled={pending}>{pending ? "送信中…" : "登録"}</button>
<p>{state.msg}</p>
</form>
);
}
useFormStatus を使えば、子コンポーネントから親フォームの状態をプロップス伝播なしで取得できます:
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? "保存中…" : "保存"}</button>;
}
API が返すフォームの初期値などの設定データは、JSON フォーマッタ で整形してから
initialStateに渡すと、大きなオブジェクトを手書きするミスを防げます。
use:レンダー中に Promise と Context を読む
use() は React 19 で導入された「レンダー中の読み取り」プリミティブです。Promise を直接 await したり Context を読んだりでき、Suspense 境界でサスペンドします。
import { use } from "react";
function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
const comments = use(commentsPromise); // resolve までサスペンド
return (
<ul>
{comments.map((c) => (
<li key={c.id}>{c.body}</li>
))}
</ul>
);
}
注意:use() はコンポーネントやフックのレンダー段階で呼ぶ必要があり、try/catch 外の条件分岐には書けません。これは普通の関数ではなく「ここは待つかもしれない」と React に伝えるシグナルです。
並行レンダリング:待っても UI を固めない
useTransition:重い更新を非緊急としてマーク
import { useTransition, useState } from "react";
function Search() {
const [query, setQuery] = useState("");
const [list, setList] = useState<Item[]>([]);
const [isPending, startTransition] = useTransition();
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
const value = e.target.value;
setQuery(value); // 入力は即座に反映
startTransition(() => {
setList(filterExpensive(value)); // 重い計算を transition 内へ
});
}
return (
<div>
<input value={query} onChange={onChange} />
<ul style={{ opacity: isPending ? 0.6 : 1 }}>
{list.map((i) => (
<li key={i.id}>{i.name}</li>
))}
</ul>
</div>
);
}
useDeferredValue:値を自動的に「遅延」
入力とレンダーが分離している場合、useDeferredValue の方が手軽です:
const deferred = useDeferredValue(query);
const results = useMemo(() => filterExpensive(deferred), [deferred]);
違い:useTransition は「一連の状態更新」を包み、useDeferredValue は「遅延された値」を派生させます(通常 useMemo と併用)。
useOptimistic:楽観的更新が標準搭載
import { useOptimistic, useRef } from "react";
type Msg = { id: number; text: string; sending?: boolean };
function Chat() {
const [messages, setMessages] = useOptimistic<Msg[]>([]);
async function send(formData: FormData) {
const text = formData.get("text") as string;
setMessages((list) => [...list, { id: Date.now(), text, sending: true }]);
await fetch("/api/chat", { method: "POST", body: JSON.stringify({ text }) });
}
return (
<form action={send}>
<input name="text" />
{messages.map((m) => (
<p key={m.id}>{m.text}{m.sending ? "(送信中)" : ""}</p>
))}
</form>
);
}
テーマカラーやボタンのコントラストは、カラー変換 と カラーコントラスト ツールで WCAG アクセシビリティを確認できます。
ref の Props 化とドキュメントメタデータ
ref はもはや特例ではない
function MyInput({ ref }: { ref: React.Ref<HTMLInputElement> }) {
return <input ref={ref} />;
}
// 利用:<MyInput ref={myRef} /> —— forwardRef は不要
ドキュメントメタデータをネイティブ対応
import { Title, Meta } from "react-dom";
function Page() {
return (
<>
<Title>ページタイトル</Title>
<Meta name="description" content="ページの説明" />
</>
);
}
React 18 からのスムーズな移行
- 依存を更新:
npm i react@19 react-dom@19と@types/reactを更新。 forwardRefを置換:refを Props に直接書くだけ。旧書き方は非推奨だが当面互換。- Actions を導入:「送信後に setState + loading」のフォームを
useActionStateへ移行。 useで非同期を収束:Server Components 配下でデータ Promise を子へ渡しuse()で読む。- 並行を組み込む:リスト/検索系は
useDeferredValueまたはuseTransitionで応答性を向上。
よくある質問 FAQ
Q1:Actions は Server Component でしか使えませんか?
いいえ。クライアント側でも useActionState で普通の非同期関数を包めます。Server Action は「関数がサーバー側で実行される」形式の一つに過ぎません。
Q2:use() と await の違いは?
await は JS の実行時セマンティクス、use() は React のレンダーセマンティクスです。Suspense 境界でサスペンドし、並行スケジューリングによって中断・再レンダーされます。
Q3:useTransition と useDeferredValue はどちらを?
「一連の更新全体を遅延」したいなら useTransition、「変化の速い値から遅い結果を派生」したいなら useDeferredValue + useMemo を。
Q4:React 19 は IE をサポートしますか?
いいえ、レガシーな挙動も完全に削除されています。モダンブラウザ環境が前提です。
Q5:楽観的更新が失敗したらどうロールバックしますか?
useOptimistic は Action が投げた例外時に自動で実状態へロールバックします。setMessages の「実データソース」を失敗時に変えないよう保証するだけで OK です。
おすすめツール
React 19 開発では、以下の ツール が役立ちます:
- JSON フォーマッタ — API レスポンスやフォーム初期状態の整理
- カラー変換 — HEX/RGB/HSL 相互変換でテーマ作成
- カラーコントラスト — ボタン/テキストの WCAG アクセシビリティ確認
- HTTP ステータスコード — Action API の応答コード調査
React 19 の本質は「Hooks が増えた」ことではなく、書き込み(Actions)と読み取り(並行/use)を統一されたメンタルモデルで管理することです。データ待ちでも UI は固まらず、変更送信時も状態を自管理する。Actions と並行プリミティブを握れば、现代 React の鍵を手にしたことになります。
ブラウザローカルツールを無料で試す →