React 19の新機能完全ガイド - 2026年版実践解説


2024年12月にリリースされたReact 19は、フロントエンド開発に革命をもたらしました。2026年現在、多くのプロジェクトがReact 19に移行し、その恩恵を享受しています。本記事では、React 19の新機能を実践的に解説します。

React 19の主要新機能

1. Actions - フォーム処理の革新

React 19最大の目玉機能がActionsです。非同期処理を伴うフォーム処理が劇的にシンプルになりました。

従来の書き方(React 18)

function SignupForm() {
  const [pending, setPending] = useState(false)
  const [error, setError] = useState<string | null>(null)

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault()
    setPending(true)
    setError(null)

    try {
      const formData = new FormData(e.currentTarget)
      await signupUser(formData)
    } catch (err) {
      setError(err.message)
    } finally {
      setPending(false)
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" required />
      <button disabled={pending}>
        {pending ? 'Submitting...' : 'Sign up'}
      </button>
      {error && <p>{error}</p>}
    </form>
  )
}

React 19の新しい書き方

import { useActionState } from 'react'

async function signupAction(prevState: any, formData: FormData) {
  const email = formData.get('email')

  try {
    await signupUser(email)
    return { success: true }
  } catch (error) {
    return { error: error.message }
  }
}

function SignupForm() {
  const [state, submitAction, isPending] = useActionState(signupAction, null)

  return (
    <form action={submitAction}>
      <input name="email" required />
      <button disabled={isPending}>
        {isPending ? 'Submitting...' : 'Sign up'}
      </button>
      {state?.error && <p>{state.error}</p>}
    </form>
  )
}

メリット:

  • ボイラープレートが大幅削減
  • ペンディング状態が自動管理
  • フォームのネイティブ動作を活用
  • 楽観的更新が簡単に実装可能

useOptimistic - 楽観的更新

import { useOptimistic } from 'react'

function TodoList({ todos }: { todos: Todo[] }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (state, newTodo: Todo) => [...state, newTodo]
  )

  async function addTodo(formData: FormData) {
    const title = formData.get('title') as string
    const tempTodo = { id: crypto.randomUUID(), title, completed: false }

    // UIを即座に更新(楽観的更新)
    addOptimisticTodo(tempTodo)

    // サーバーに送信
    await createTodo(title)
  }

  return (
    <>
      <form action={addTodo}>
        <input name="title" required />
        <button>Add Todo</button>
      </form>

      <ul>
        {optimisticTodos.map(todo => (
          <li key={todo.id} style={{ opacity: todo.id.startsWith('temp') ? 0.5 : 1 }}>
            {todo.title}
          </li>
        ))}
      </ul>
    </>
  )
}

2. use API - 柔軟なデータフェッチ

use APIは、Promiseやコンテキストを条件付きで読み取れる画期的なHookです。

Promiseを読み取る

import { use, Suspense } from 'react'

// データフェッチ関数
async function fetchUser(id: string) {
  const res = await fetch(`/api/users/${id}`)
  return res.json()
}

function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
  // Promiseを直接読み取る
  const user = use(userPromise)

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  )
}

function App() {
  const userPromise = fetchUser('123')

  return (
    <Suspense fallback={<Skeleton />}>
      <UserProfile userPromise={userPromise} />
    </Suspense>
  )
}

条件付きでコンテキストを読み取る

import { use, createContext } from 'react'

const ThemeContext = createContext<'light' | 'dark'>('light')

function Button({ primary }: { primary?: boolean }) {
  // 条件付きでコンテキストを使える!
  const theme = primary ? use(ThemeContext) : 'light'

  return <button className={theme}>{/* ... */}</button>
}

従来のHooksとの違い:

  • useは条件分岐の中で呼べる
  • useはループの中で呼べる
  • useはイベントハンドラでも呼べる

3. useFormStatus - フォーム状態の共有

親フォームのペンディング状態を子コンポーネントで取得できます。

import { useFormStatus } from 'react-dom'

function SubmitButton() {
  const { pending, data, method, action } = useFormStatus()

  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Submitting...' : 'Submit'}
    </button>
  )
}

function Form() {
  async function submitAction(formData: FormData) {
    await saveData(formData)
  }

  return (
    <form action={submitAction}>
      <input name="title" />
      <SubmitButton />  {/* 親フォームの状態を取得 */}
    </form>
  )
}

4. ref as prop - ref渡しがシンプルに

forwardRefが不要になりました。

React 18

import { forwardRef } from 'react'

const Input = forwardRef<HTMLInputElement, Props>((props, ref) => {
  return <input ref={ref} {...props} />
})

React 19

function Input({ ref, ...props }: Props & { ref?: Ref<HTMLInputElement> }) {
  return <input ref={ref} {...props} />
}

// または型推論を活用
function Input(props: Props) {
  return <input {...props} />
}

メリット:

  • コードがシンプルに
  • TypeScript型定義が楽に
  • forwardRefのネストが不要

5. ref cleanup function - メモリリーク防止

refに渡すコールバックからクリーンアップ関数を返せます。

function VideoPlayer() {
  return (
    <video
      ref={(node) => {
        if (node) {
          const player = new VideoJS(node)
          // クリーンアップ関数を返す
          return () => {
            player.dispose()
          }
        }
      }}
    />
  )
}

6. Context as Provider - シンプルなContext

Context.Providerの代わりにContextをそのまま使えます。

React 18

const ThemeContext = createContext('light')

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Page />
    </ThemeContext.Provider>
  )
}

React 19

const ThemeContext = createContext('light')

function App() {
  return (
    <ThemeContext value="dark">
      <Page />
    </ThemeContext>
  )
}

7. Document Metadata - メタデータをコンポーネントで管理

<title><meta>タグをコンポーネント内で直接書けます。

function BlogPost({ post }: { post: Post }) {
  return (
    <article>
      {/* メタデータをコンポーネント内で定義 */}
      <title>{post.title} - My Blog</title>
      <meta name="description" content={post.excerpt} />
      <meta property="og:title" content={post.title} />
      <meta property="og:image" content={post.coverImage} />

      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}

React 19が自動的に<head>に移動してくれます。react-helmetやNext.jsのHeadコンポーネントが不要に!

8. Stylesheet Priority - スタイル読み込み順序制御

function ComponentA() {
  return (
    <>
      <link rel="stylesheet" href="/styles/base.css" precedence="default" />
      <div className="content">A</div>
    </>
  )
}

function ComponentB() {
  return (
    <>
      <link rel="stylesheet" href="/styles/theme.css" precedence="high" />
      <div className="content">B</div>
    </>
  )
}

precedence属性でスタイルシートの優先度を制御できます。

9. Async Scripts - 重複排除

function MyComponent() {
  return (
    <div>
      <script async src="https://example.com/script.js" />
    </div>
  )
}

同じスクリプトを複数コンポーネントで読み込んでも、React 19が自動的に重複を排除してくれます。

10. Preloading Resources - リソース事前読み込み

import { prefetchDNS, preconnect, preload, preinit } from 'react-dom'

function App() {
  // DNS事前解決
  prefetchDNS('https://api.example.com')

  // コネクション事前確立
  preconnect('https://cdn.example.com')

  // リソース事前読み込み
  preload('/fonts/custom-font.woff2', { as: 'font' })

  // スクリプト事前初期化
  preinit('/scripts/analytics.js', { as: 'script' })

  return <div>{/* ... */}</div>
}

Server Components(実験的機能)

React 19はServer Componentsを正式サポート(Next.js App Router等で利用可能)。

Server Component

// app/posts/[id]/page.tsx (Next.js)
async function BlogPost({ params }: { params: { id: string } }) {
  // サーバーで直接DBアクセス
  const post = await db.posts.findUnique({ where: { id: params.id } })

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      <Comments postId={post.id} />  {/* Client Component */}
    </article>
  )
}

Client Component

'use client'

import { useState } from 'react'

function Comments({ postId }: { postId: string }) {
  const [comments, setComments] = useState<Comment[]>([])

  // クライアント側でインタラクション
  return <div>{/* ... */}</div>
}

破壊的変更と移行ガイド

1. React DOM APIs

hydrate → hydrateRoot

// React 18
import { hydrate } from 'react-dom'
hydrate(<App />, document.getElementById('root'))

// React 19
import { hydrateRoot } from 'react-dom/client'
hydrateRoot(document.getElementById('root')!, <App />)

unmountComponentAtNode → root.unmount

// React 18
import { unmountComponentAtNode } from 'react-dom'
unmountComponentAtNode(container)

// React 19
root.unmount()

2. TypeScript変更

JSX Namespace

// React 18
declare namespace JSX {
  interface IntrinsicElements {
    'my-element': any
  }
}

// React 19
declare global {
  namespace React.JSX {
    interface IntrinsicElements {
      'my-element': any
    }
  }
}

3. 削除されたAPI

  • defaultProps(関数コンポーネント)→ デフォルト引数を使用
  • propTypes → TypeScriptを使用
  • Legacy Context → createContextを使用

パフォーマンス改善

1. Automatic Batching強化

function handleClick() {
  // React 19では全て自動バッチング
  setState1(v1)
  setState2(v2)
  setState3(v3)
  // → 1回のレンダリングで完了
}

2. Compiler(React Forget)

React 19には新しいコンパイラが組み込まれており、自動メモ化を実現(オプトイン)。

// 従来
const MemoizedComponent = memo(function MyComponent({ data }) {
  const processed = useMemo(() => expensiveOperation(data), [data])
  const handleClick = useCallback(() => {}, [])

  return <div>{processed}</div>
})

// React Compiler有効化後
function MyComponent({ data }) {
  // 自動的にメモ化される!
  const processed = expensiveOperation(data)
  const handleClick = () => {}

  return <div>{processed}</div>
}

実践的な移行戦略

ステップ1: 依存パッケージ更新

npm install react@19 react-dom@19

# TypeScript型定義
npm install -D @types/react@19 @types/react-dom@19

ステップ2: React Router等の更新

npm install react-router-dom@latest
npm install @tanstack/react-query@latest

ステップ3: 段階的移行

  1. まず動作確認(破壊的変更が少ないため、ほとんど動く)
  2. 新機能を段階的に導入
  3. use、Actions等を活用してコード簡素化
  4. パフォーマンス測定

ステップ4: ESLint設定更新

npm install -D eslint-plugin-react-hooks@latest
// .eslintrc.json
{
  "rules": {
    "react/jsx-no-target-blank": ["error", { "allowReferrer": true }],
    "react-hooks/exhaustive-deps": "warn"
  }
}

まとめ

React 19は以下の点で革新的です:

DXの向上:

  • フォーム処理が劇的にシンプルに(Actions)
  • forwardRef不要、refがpropに
  • Contextの書き方がシンプルに

パフォーマンス:

  • 自動バッチング強化
  • React Compilerによる自動最適化
  • Asset Loading最適化

新機能:

  • use APIで柔軟なデータ読み取り
  • Document Metadata管理
  • Server Components正式サポート

移行の容易さ:

  • 破壊的変更が少ない
  • 段階的移行が可能
  • 既存コードのほとんどがそのまま動く

2026年現在、React 19は成熟し、本番環境で広く使われています。まだReact 18の方は、ぜひアップグレードを検討してみてください。

参考リンク: