Contents
1. アップグレード前の準備
React 19 に移行する前に、以下の項目をチェックしてください。
- Node.js のバージョン:
>=18.0.0が推奨されます。 - 依存パッケージの互換性:
react-dom,react-scripts, 主要 UI ライブラリ(MUI, Chakra UI 等)が React 19 をサポートしているか公式ドキュメントで確認します。 - テストカバレッジ:既存のユニットテストと E2E テストがすべて成功することをローカルで検証し、CI パイプラインでも同様に通過させます。
重要:
npm audit fix --forceは依存関係のバージョン解決を強制的に上書きし、予期せぬ破壊的変更を招く可能性があります。代替策としてはnpm audit fix(フラグなし)や 手動でパッケージを更新 することが安全です。特に CRA (react-scripts) 系列では、バージョン上書きがビルドエラーにつながるケースが報告されています。
2. npm / Yarn でのインストール手順
2‑1. 基本的なインストールコマンド
|
1 2 3 4 5 6 |
# npm を使用する場合 npm install react@latest react-dom@latest # Yarn を使用する場合 yarn add react@latest react-dom@latest |
@latest は 現在の最新安定版(v19.x) を指します。インストール後は必ずバージョンを確認し、peerDependencies の警告が出ていないかチェックしてください。
2‑2. バージョン確認と依存関係の整合性
|
1 2 3 |
# インストールされた React と ReactDOM の実際のバージョンを表示 npm list react react-dom |
依存関係が不一致の場合の対処例
react-scriptsが古い場合は最新版にアップデート
bash
npm install react-scripts@latest --save-dev- それでも警告が残るときは、個別パッケージを手動で最新互換版に置き換える(例:
npm install @mui/material@^5.15.0)。 npm audit fixを実行して脆弱性だけを自動修正し、--forceは使用しない。
3. React 19 がもたらす新フックの全体像
React 19 では 非同期データ取得・サーバーアクション管理・楽観的 UI 更新 といった実務で頻出するシナリオに特化した 5 つのフックが追加されました。以下の表は各フックの TypeScript シグネチャと主な利用例をまとめたものです。
| フック | シグネチャ (TS) | 主な役割・ユースケース |
|---|---|---|
use |
<T>(promise: Promise<T> \| (() => Promise<T>)) => T |
Suspense と連携した 1 行非同期取得。データ取得ロジックを完全に省略できる。 |
useActionState |
(action: (...args:any[])=>Promise<any>, initialState:S) => [S, Dispatch] |
サーバーサイドアクション(例:フォーム送信)とその状態管理をシンプルに統合。 |
useFormStatus |
() => { pending:boolean; data?:any } |
アクション実行中のローディングステータス取得。UI のスピナー表示に最適。 |
useOptimistic |
<S>(initial:S, reducer:(prev:S, payload:any)=>S) => [S, (payload:any)=>void] |
楽観的 UI 更新 とローカル状態管理を同時に実装できる。 |
useDeferredValue |
<T>(value:T, config?:{ timeoutMs:number }) => T |
高価な計算や検索結果の遅延評価でインタラクティブ性向上。 |
参考:最新情報は React 公式サイト(https://react.dev/versions)をご確認ください。
4. use フックで非同期データ取得をシンプルに
4‑1. 正しいインポート方法
React 19 では use は名前付きエクスポートとして提供されます。以下のようにインポートしてください。
|
1 2 |
import { use } from "react"; |
※ 従来の import React, { use } from "react" の形は、use がデフォルトエクスポートに含まれないためコンパイルエラーになります。
4‑2. 基本的な使用例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import { use, Suspense } from "react"; type Book = { id: string; title: string; }; async function fetchBooks(): Promise<Book[]> { const res = await fetch("https://api.example.com/books"); if (!res.ok) throw new Error("Network error"); return res.json(); } /* 1 行で非同期取得 */ export default function BookList() { // `use` が Promise を受け取り、解決された値を返す const books = use(fetchBooks()); return ( <ul> {books.map((b) => ( <li key={b.id}> {b.title} — {b.author} </li> ))} </ul> ); } /* Suspense によるローディング UI 包装 */ export function App() { return ( <Suspense fallback={<div>ロード中…</div>}> <BookList /> </Suspense> ); } |
use(fetchBooks())の呼び出しは 即時実行 され、内部で Suspense が自動的にローディング状態を管理します。- コンポーネントは必ず
<Suspense>(または同等のカスタムラッパー)で包む必要があります。
4‑3. エラーハンドリングのベストプラクティス
use 自体は例外を投げませんが、Promise が reject した場合は ErrorBoundary に委譲されます。以下に簡易的なエラーバウンダリ実装例を示します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { Component, ReactNode } from "react"; type Props = { children: ReactNode }; type State = { hasError: boolean; error?: Error }; export class ErrorBoundary extends Component<Props, State> { state: State = { hasError: false }; static getDerivedStateFromError(error: Error) { return { hasError: true, error }; } render() { if (this.state.hasError) { return <div>エラーが発生しました: {this.state.error?.message}</div>; } return this.props.children; } } |
App コンポーネントで組み合わせると次のようになります。
|
1 2 3 4 5 6 7 8 9 10 11 12 |
import { ErrorBoundary } from "./ErrorBoundary"; export function App() { return ( <ErrorBoundary> <Suspense fallback={<div>ロード中…</div>}> <BookList /> </Suspense> </ErrorBoundary> ); } |
5. useOptimistic を使った楽観的 UI 更新例
5‑1. シナリオ概要
書籍一覧に「お気に入り」ボタンを追加し、ユーザーがクリックした瞬間に UI が即座に変化するが、サーバー側でも非同期で状態を永続化したいケースです。useOptimistic を用いると ローカルの楽観的更新 と サーバーエラー時のリバート をシンプルに実装できます。
5‑2. 実装コード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
import { useOptimistic } from "react"; type Book = { id: string; title: string; liked: boolean; }; /* サーバーへ PATCH リクエストを送信 */ function toggleLikeOnServer(id: string, liked: boolean): Promise<void> { return fetch(`/api/books/${id}/like`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ liked }), }).then((res) => { if (!res.ok) throw new Error("サーバーエラー"); }); } /* カスタムフック */ export function useBookLike(initialBooks: Book[]) { const [books, setBooks] = useOptimistic<Book[]>(initialBooks, (prev, payload) => prev.map((b) => (b.id === payload.id ? { ...b, liked: payload.liked } : b)) ); const toggle = async (id: string) => { const target = books.find((b) => b.id === id); if (!target) return; // UI を即時更新 setBooks({ id, liked: !target.liked }); try { await toggleLikeOnServer(id, !target.liked); } catch (e) { // エラー時は元に戻す(簡易実装) setBooks({ id, liked: target.liked }); console.error(e); } }; return { books, toggle }; } /* コンポーネント側での利用例 */ export function BookListWithLike() { const { books, toggle } = useBookLike([ /* 初期データは SSR/CSR どちらでも取得可能 */ ]); return ( <ul> {books.map((b) => ( <li key={b.id}> {b.title} — {b.author} <button onClick={() => toggle(b.id)}>{b.liked ? "★" : "☆"}</button> </li> ))} </ul> ); } |
- 第2引数の reducer が状態遷移ロジックを担い、
setBooksに渡すpayloadの形は自由に設計できます。 - TypeScript は
useOptimistic<Book[]>から 配列全体の型情報 を自動推論し、IDE 補完がフルに機能します。
6. React 18 から React 19 への移行チェックリスト
6‑1. データ取得パターンの置き換え
| 項目 | React 18 の典型例 | React 19 推奨 |
|---|---|---|
| 非同期取得 | useEffect + useState |
use(promise) |
| ローディング UI | 手動条件分岐 (if (!data)) |
<Suspense fallback> |
| エラーハンドリング | try/catch inside effect |
ErrorBoundary と組み合わせる |
| 型推論 | 明示的に useState<Type> |
use が Promise の戻り値から自動推論 |
6‑2. SSR/CSR におけるパフォーマンス比較
- SSR(サーバーサイドレンダリング)
useはサーバー側でも Suspense を利用でき、ストリーミング HTML が完全なデータ状態で出力されます。Next.js のapp/ディレクトリや Remix のloaderと組み合わせると、クライアント側の再フェッチが不要になるケースがあります。- CSR(クライアントサイドレンダリング)
- 初回ロード時に Suspense がローディング UI を表示し、その後は差分更新のみで高速描画が可能です。
useOptimisticによる楽観的 UI はインタラクティブ性を大幅に向上させます。
ベストプラクティス:既存の
useEffectが本当に必要かどうかを判断基準にし、テストカバレッジが維持できる範囲で段階的にuseへ置き換えてください。
7. まとめ
- React 19 は「非同期取得と楽観的 UI」を標準フックで提供し、従来の
useEffect/useStateパターンを大幅に簡素化します。 - アップグレードは
npm install react@latest react-dom@latestだけで完了しますが、依存関係の整合性とテストの確認は必須です。 npm audit fix --forceの使用は 破壊的変更リスクが高いため、代替手段(手動更新やnpm audit fix)を優先してください。- 正しいインポート例は
import { use } from "react"です。誤ったデフォルトインポートはコンパイルエラーの原因になります。 - 本稿で紹介した
use,useOptimistic,useActionStateなど の実装例をベースに、既存コードベースへ段階的に導入すれば、保守性と開発速度が向上します。
React 19 の新機能はまだ進化途中です。公式ドキュメントやリリースノートを定期的にチェックし、安全でスムーズな移行を目指しましょう。