mcpField
R
react-19

react-19

github.com/noklip-io開発178 回インストール40 週間前に更新
リポジトリ

react-19 について

React 開発者向けに、React 19 の新 API、設計方針の変化、移行時の注意点を解説するガイドです。各テーマの詳細資料も案内します。

主な機能

  1. 01React 19 の新機能一覧
  2. 02新しいフックとフォームアクション
  3. 03React Compiler とメモ化
  4. 04削除 API の移行案内
  5. 05TypeScript の変更と codemod

活用シーン

  1. 01React 19 のフックやフォーム設計の把握
  2. 02React 18 プロジェクトの React 19 移行
  3. 03削除 API の代替手段の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/noklip-io/agent-skills --skill react-19

インストール後、Claude Code で/react-19を入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
anti-patterns.md
deprecations.md
new-hooks.md
paradigm-shifts.md
react-19-2-features.md
react-compiler.md
server-components.md
suspense-streaming.md
typescript-changes.md

詳細

React 19 - 主な変更点

このスキルでは、React 19で何が変わったかに焦点を当てます。Reactの完全なリファレンスではありません。

React 16/17から移行する場合

React 18より前のバージョンからアップグレードする場合、これらの変更が積み重なっており、現在は必須です。

変更点導入バージョンReact 19での状況
createRoot / hydrateRootReact 18必須(ReactDOM.renderは削除)
並行レンダリングReact 18R19の全機能の基盤
自動バッチ処理React 18デフォルトの動作
useId、useSyncExternalStoreReact 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 18React 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 を使用
propTypesTypeScript を使用するか、削除
defaultProps(関数)ES6 のデフォルト引数
文字列 refコールバック ref または useRef
レガシー ContextcreateContext
React.createFactoryJSX
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.mdReact 19.1 以降および 19.2 の機能(Activity、useEffectEvent)
new-hooks.md19.0 のフックに関する完全な API
server-components.mdRSC、Server Actions、ディレクティブ
suspense-streaming.mdSuspense、ストリーミング、エラー処理
react-compiler.md自動メモ化の詳細
deprecations.md移行ガイド付きの削除された API
typescript-changes.md型の変更とコードモッド

コメント