react-19 について
コーディングアシスタント向けに、React 19 の変更点、API、移行時の注意点をまとめたリファレンススキルです。React 全般ではなく、旧バージョンからの差分に焦点を当てています。
主な機能
- 01React 19 の API 変更
- 02新しい Hooks と Form Actions
- 03移行ガイドと codemods
- 04React 19.2 の機能解説
- 05TypeScript と削除 API の解説
活用シーン
- 01アプリの React 19 への移行
- 02Hooks と ref の変更点の確認
- 03削除された React API の置き換え
- 04React 19.2 の機能の把握
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jbmanllr/ agent-skills --skill react-19インストール後、Claude Code で/react-19を入力して読み込まれたか確認します。
詳細
React 19 - 主な変更点
このスキルでは、React 19で何が変わったかに焦点を当てます。Reactの完全なリファレンスではありません。
React 16/17から移行する場合
React 18より前のバージョンからアップグレードする場合、これらの変更が積み重なっており、現在は必須です。
| 変更点 | 導入バージョン | React 19での状況 |
|---|---|---|
createRoot / hydrateRoot | React 18 | 必須(ReactDOM.renderは削除) |
| 並行レンダリング | React 18 | R19の全機能の基盤 |
| 自動バッチ処理 | React 18 | デフォルトの動作 |
useId、useSyncExternalStore | React 18 | 安定しており、広く使用されています |
| フック(新しいコードではクラスを使わない) | React 16.8 | 新機能を使う唯一の方法 |
createContext(レガシーではない) | React 16.3 | 必須(レガシーContextは削除) |
| エラーバウンダリ | React 16 | エラーコールバックが改善されました |
移行手順: まずReact 18.3にアップグレードして非推奨警告を確認し、その後19にアップグレードします。
React 19の考え方
React 19では、Reactについての考え方が根本的に変わります。
| これまでの考え方 | 新しい考え方 |
|---|---|
| デフォルトはクライアントサイド | サーバー優先(RSCがデフォルト) |
| 手動でメモ化する | コンパイラーが処理する |
データ取得にuseEffectを使う | 非同期Server Component |
フォームにuseStateを使う | フォームアクション |
| ローディング状態をブール値で管理する | Suspense境界 |
| すべてを最適化する | 正しいコードを書けば、コンパイラーが最適化する |
考え方の変化については、references/paradigm-shifts.mdを参照してください。
やめるべきアンチパターンについては、references/anti-patterns.mdを参照してください。
クイックリファレンス:新機能
| 機能 | React 18 | React 19+ |
|---|---|---|
| メモ化 | 手動(useMemo、useCallback、memo) | React Compiler(自動)または手動 |
| refの転送 | forwardRef()ラッパー | 通常のpropとしてのref |
| Contextプロバイダー | <Context.Provider value={}> | <Context value={}> |
| フォームの状態 | useStateを使ったカスタム実装 | useActionStateフック |
| 楽観的更新 | 手動で状態を管理 | useOptimisticフック |
| Promiseの読み取り | レンダリング中には不可 | use()フック |
| 条件付きContext | 不可 | 条件分岐の後でuse(Context)を使用 |
| フォームの保留状態 | 手動で追跡 | useFormStatusフック |
| refのクリーンアップ | アンマウント時にnullを渡す | クリーンアップ関数を返す |
| ドキュメントメタデータ | react-helmetまたは手動 | ネイティブの<title>、<meta>、<link> |
| 状態によるUIの表示/非表示 | アンマウント/再マウント(状態は失われる) | <Activity>コンポーネント(19.2以降) |
| 非リアクティブなEffectロジック | 依存配列に追加するか、lintを抑制する | useEffectEventフック(19.2以降) |
| カスタム要素 | 部分的なサポート | 完全なサポート(propはプロパティとして扱われる) |
| ハイドレーションエラー | 複数の曖昧なエラー | 差分を含む単一のエラー |
React Compilerとメモ化
React Compilerを有効にすると、手動のメモ化は任意であり、禁止ではありません。
// React Compiler handles this automatically
function Component({ items }) {
const filtered = items.filter(x => x.active);
const sorted = filtered.sort((a, b) => a.name.localeCompare(b.name));
const handleClick = (id) => console.log(id);
return <List items={sorted} onClick={handleClick} />;
}
// Manual memoization still works as escape hatch for fine-grained control
const filtered = useMemo(() => expensiveOperation(items), [items]);
const handleClick = useCallback((id) => onClick(id), [onClick]);
React Compilerを使う場合に手動でメモ化するケース:
- 安定した参照が必要なEffectの依存関係
- コンポーネント間で高コストな計算を共有する場合(コンパイラーは共有しません)
- 再計算のタイミングを明示的に制御する場合
詳細はreferences/react-compiler.mdを参照してください。
propとしてのref(forwardRefは非推奨)
// React 19: ref is just a prop
function Input({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />;
}
// Usage - no change
const inputRef = useRef(null);
<Input ref={inputRef} placeholder="Enter text" />
// forwardRef still works but will be deprecated
// Codemod: npx codemod@latest react/19/replace-forward-ref
refのクリーンアップ関数
// React 19: Return cleanup function from ref callback
<input
ref={(node) => {
// Setup
node?.focus();
// Return cleanup (called on unmount or ref change)
return () => {
console.log('Cleanup');
};
}}
/>
// React 18: Received null on unmount (still works, but cleanup preferred)
<input ref={(node) => {
if (node) { /* setup */ }
else { /* cleanup */ }
}} />
プロバイダーとしてのContext
const ThemeContext = createContext('light');
// React 19: Use Context directly
function App({ children }) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
// React 18: Required .Provider (still works, will be deprecated)
<ThemeContext.Provider value="dark">
{children}
</ThemeContext.Provider>
新しいフック
useActionState
import { useActionState } from 'react';
function Form() {
const [error, submitAction, isPending] = useActionState(
async (prevState, formData) => {
const result = await saveData(formData.get('name'));
if (result.error) return result.error;
redirect('/success');
return null;
},
null // initial state
);
return (
<form action={submitAction}>
<input name="name" disabled={isPending} />
<button disabled={isPending}>
{isPending ? 'Saving...' : 'Save'}
</button>
{error && <p className="error">{error}</p>}
</form>
);
}
useOptimistic
import { useOptimistic } from 'react';
function Messages({ messages, sendMessage }) {
const [optimisticMessages, addOptimistic] = useOptimistic(
messages,
(state, newMessage) => [...state, { ...newMessage, sending: true }]
);
async function handleSubmit(formData) {
const message = { text: formData.get('text'), id: Date.now() };
addOptimistic(message); // Show immediately
await sendMessage(message); // Reverts on error
}
return (
<form action={handleSubmit}>
{optimisticMessages.map(m => (
<div key={m.id} style={{ opacity: m.sending ? 0.5 : 1 }}>
{m.text}
</div>
))}
<input name="text" />
</form>
);
}
use()フック
import { use, Suspense } from 'react';
// Read promises (suspends until resolved)
function Comments({ commentsPromise }) {
const comments = use(commentsPromise);
return comments.map(c => <p key={c.id}>{c.text}</p>);
}
// Usage with Suspense
<Suspense fallback={<Spinner />}>
<Comments commentsPromise={fetchComments()} />
</Suspense>
// Conditional context reading (not possible with useContext!)
function Theme({ showTheme }) {
if (!showTheme) return <div>Plain</div>;
const theme = use(ThemeContext); // Can be called conditionally!
return <div style={{ color: theme.primary }}>Themed</div>;
}
useFormStatus(react-dom)
import { useFormStatus } from 'react-dom';
// Must be used inside a <form> - reads parent form status
function SubmitButton() {
const { pending, data, method, action } = useFormStatus();
return (
<button disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
);
}
function Form() {
return (
<form action={serverAction}>
<input name="email" />
<SubmitButton /> {/* Reads form status via context */}
</form>
);
}
APIの詳細についてはreferences/new-hooks.mdを参照してください。
フォームアクション
// Pass function directly to form action
<form action={async (formData) => {
'use server';
await saveToDatabase(formData);
}}>
<input name="email" type="email" />
<button type="submit">Subscribe</button>
</form>
// Button-level actions
<form>
<button formAction={saveAction}>Save</button>
<button formAction={deleteAction}>Delete</button>
</form>
// Manual form reset
import { requestFormReset } from 'react-dom';
requestFormReset(formElement);
ドキュメントメタデータ
// Automatically hoisted to <head> - works in any component
function BlogPost({ post }) {
return (
<article>
<title>{post.title}</title>
<meta name="description" content={post.excerpt} />
<meta name="author" content={post.author} />
<link rel="canonical" href={post.url} />
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
リソースのプリロード
import { prefetchDNS, preconnect, preload, preinit } from 'react-dom';
function App() {
// DNS prefetch
prefetchDNS('https://api.example.com');
// Establish connection early
preconnect('https://fonts.googleapis.com');
// Preload resources
preload('https://example.com/font.woff2', { as: 'font' });
preload('/hero.jpg', { as: 'image' });
// Load and execute script eagerly
preinit('https://example.com/analytics.js', { as: 'script' });
return <main>...</main>;
}
スタイルシートのサポート
// precedence controls insertion order and deduplication
function Component() {
return (
<>
<link rel="stylesheet" href="/base.css" precedence="default" />
<link rel="stylesheet" href="/theme.css" precedence="high" />
<div className="styled">Content</div>
</>
);
}
// React ensures stylesheets load before Suspense boundary reveals
<Suspense fallback={<Skeleton />}>
<link rel="stylesheet" href="/feature.css" precedence="default" />
<FeatureComponent />
</Suspense>
カスタム要素のサポート
React 19 では、カスタム要素(Web Components)が完全にサポートされます。
// Props matching element properties are assigned as properties
// Others are assigned as attributes
<my-element
stringAttr="hello" // Attribute (string)
complexProp={{ foo: 'bar' }} // Property (object)
onCustomEvent={handleEvent} // Property (function)
/>
クライアントサイド: React は、要素のインスタンスにプロパティが存在するかを確認します。存在する場合はプロパティとして割り当て、存在しない場合は属性として割り当てます。
サーバーサイド(SSR): プリミティブ型(文字列、数値)は属性としてレンダリングされます。オブジェクト、関数、シンボルは HTML から省略されます。
// Define custom element
class MyElement extends HTMLElement {
constructor() {
super();
this.data = undefined; // React will assign to this property
}
connectedCallback() {
this.textContent = JSON.stringify(this.data);
}
}
customElements.define('my-element', MyElement);
// Use in React
<my-element data={{ items: [1, 2, 3] }} />
ハイドレーションの改善
より分かりやすいエラーメッセージ
React 19 では、曖昧なエラーが複数表示される代わりに、差分付きの単一のエラーが表示されます。
Uncaught Error: Hydration failed because the server rendered HTML
didn't match the client.
<App>
<span>
+ Client
- Server
サードパーティ製スクリプトとの互換性
React 19 は、ブラウザー拡張機能やサードパーティ製スクリプトによって挿入された要素を適切に処理します。
<head>および<body>内の予期しないタグはスキップされます(一致しないことによるエラーは発生しません)- 拡張機能によるスタイルシートは、再レンダリング時にも保持されます
- 拡張機能によって挿入されたコンテンツに
suppressHydrationWarningを追加する必要はありません
削除された API(破壊的変更)
| 削除対象 | 移行先 |
|---|---|
ReactDOM.render() | createRoot().render() |
ReactDOM.hydrate() | hydrateRoot() |
unmountComponentAtNode() | root.unmount() |
ReactDOM.findDOMNode() | ref を使用 |
propTypes | TypeScript を使用するか、削除 |
defaultProps(関数) | ES6 のデフォルト引数 |
| 文字列 ref | コールバック ref または useRef |
| レガシー Context | createContext |
React.createFactory | JSX |
react-dom/test-utils | 'react' の act |
移行ガイドについては、references/deprecations.md を参照してください。
TypeScript の変更
// useRef requires argument
const ref = useRef<HTMLDivElement>(null); // Required
const ref = useRef(); // Error in React 19
// Ref callbacks must not return values (except cleanup)
<div ref={(node) => { instance = node; }} /> // Correct
<div ref={(node) => (instance = node)} /> // Error - implicit return
// ReactElement props are now unknown (not any)
type Props = ReactElement['props']; // unknown in R19, any in R18
// JSX namespace - import explicitly
import type { JSX } from 'react';
コードモッドについては、references/typescript-changes.md を参照してください。
移行用コードモッド
# Run all React 19 codemods
npx codemod@latest react/19/migration-recipe
# Individual codemods
npx codemod@latest react/19/replace-reactdom-render
npx codemod@latest react/19/replace-string-ref
npx codemod@latest react/19/replace-act-import
npx codemod@latest react/19/replace-use-form-state
npx codemod@latest react/prop-types-typescript
# TypeScript types
npx types-react-codemod@latest preset-19 ./src
インポート(ベストプラクティス)
// Named imports (recommended)
import { useState, useEffect, useRef, use } from 'react';
import { createRoot } from 'react-dom/client';
import { useFormStatus } from 'react-dom';
// Default import still works but named preferred
import React from 'react'; // Works but not recommended
エラー処理の変更
// React 19 error handling options
const root = createRoot(container, {
onUncaughtError: (error, errorInfo) => {
// Errors not caught by Error Boundary
console.error('Uncaught:', error, errorInfo.componentStack);
},
onCaughtError: (error, errorInfo) => {
// Errors caught by Error Boundary
reportToAnalytics(error);
},
onRecoverableError: (error, errorInfo) => {
// Errors React recovered from automatically
console.warn('Recovered:', error);
}
});
Suspense のパターンとエラーバウンダリーについては、references/suspense-streaming.md を参照してください。
React 19.2 以降の機能
Activity コンポーネント(19.2)
状態を保持したまま UI を非表示/表示にします(バックグラウンドのタブのように動作します)。
import { Activity } from 'react';
// State preserved when hidden, Effects cleaned up
<Activity mode={isVisible ? 'visible' : 'hidden'}>
<ExpensiveComponent />
</Activity>
useEffectEvent フック(19.2)
依存関係を追加せずに、Effect から非リアクティブなロジックを切り出します。
import { useEffect, useEffectEvent } from 'react';
function Chat({ roomId, theme }) {
// Reads theme without making it a dependency
const onConnected = useEffectEvent(() => {
showNotification(`Connected!`, theme);
});
useEffect(() => {
const conn = connect(roomId);
conn.on('connected', onConnected);
return () => conn.disconnect();
}, [roomId]); // theme NOT in deps - won't reconnect on theme change
}
19.1 以降および 19.2 の機能全般については、references/react-19-2-features.md を参照してください。
リファレンスドキュメント
| ドキュメント | 内容 |
|---|---|
| paradigm-shifts.md | メンタルモデルの変化 — React 19 での 考え方 |
| anti-patterns.md | やめるべきこと — 古くなったパターン |
| react-19-2-features.md | React 19.1 以降および 19.2 の機能(Activity、useEffectEvent) |
| new-hooks.md | 19.0 のフックに関する完全な API |
| server-components.md | RSC、Server Actions、ディレクティブ |
| suspense-streaming.md | Suspense、ストリーミング、エラー処理 |
| react-compiler.md | 自動メモ化の詳細 |
| deprecations.md | 移行ガイド付きの削除された API |
| typescript-changes.md | 型の変更とコードモッド |