言語を切り替える
テーマを切り替える

React 19:フォーム処理にまだ30行もコードを書くの?Actionsで瞬殺、パフォーマンス40%向上

はじめに

ある金曜日、午後。私は画面上のログインフォームのコードを見つめながら、イライラしていました。たかがフォーム送信です。「なぜ useState で loading、error、data の状態を管理し、さらに useEffect で送信ロジックを制御するために30行以上も書かなければならないんだ?」と。「フォーム処理ごときで、なんでこんなに面倒なんだ!」

週末、暇つぶしにReact 19の正式リリース(2024年12月5日)を眺めていました。正直、最初は抵抗がありました。「また新しいことを覚えるのか……」と。しかし、公式ドキュメントを読み進めるうちに、Actions、コンパイラ、use() Hookといった新機能が、まさに私が抱えていた日々の開発の悩みを解決してくれるものだと気づきました。

そこで私は1週間かけてReact 19を徹底的に研究し、個人プロジェクトで6つのコア機能を試してみました。結論から言うと、今回のアップデートは革命的ではないものの、私たちが毎日直面する「現実的な問題」を解決してくれるアップデートです。今日は、これらの新機能が実際に使えるものなのか、お話しします。

React 19は何を解決したのか?(開発者視点)

機能の詳細に入る前に、今回のアップデートがどのような痛点を解消しようとしているのか整理しましょう。

フォーム処理の古い問題。これまでのフォーム送信は、だいたいこんなパターンでした:

// React 18までの古いやり方 - コードが冗長でミスしやすい
function LoginForm() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [data, setData] = useState(null);
  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    setError(null);
    try {
      const result = await loginAPI(email, password);
      setData(result);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };
  // さらにボタンの非活性化やエラー表示も手動管理...
}

これでも単純な方です。複雑なフォームになれば、loadingやerrorの状態管理だけでコードが膨れ上がり、発狂しそうになります。

パフォーマンス最適化の精神的負荷。正直なところ、コンポーネントに memo を付け忘れたり、どの計算値を useMemo でラップすべきか迷ったりすることは日常茶飯事です。やりすぎれば過剰最適化、やらなすぎればパフォーマンス低下。コードレビューのたびに悩むのは疲れました。

Server Componentsの混乱。Next.jsのServer Componentsを使いたいと思っても、ドキュメントは難解でした。「いつ ‘use client’ を使うのか?」「サーバーコンポーネントとクライアントコンポーネントはどう連携するのか?」「データはどう渡すのか?」Google検索に何時間も費やしていました。

React 19は、これらの問題に対してより良い解決策を提示しています。

Actions - フォーム処理地獄からの解放

Actionsとは? 簡単に言えば、Reactが提供する非同期操作を処理する新しい方法です。非同期関数を直接フォームに渡すことができ、Reactがpending(処理中)、error(エラー)、success(成功)といった状態を自動管理してくれます。
最初に useActionState というAPIを見た時は戸惑いましたが、試してみると……これは最高です!

実戦コード例
先ほどのログインフォームをReact 19のActionsで書き換えるとこうなります:

// React 19の新しいやり方 - コードが簡潔でロジック明快
import { useActionState } from 'react';

function LoginForm() {
  // useActionStateは [現在の状態, 送信アクション関数, 処理中フラグ] を返す
  const [state, submitAction, isPending] = useActionState(
    async (prevState, formData) => {
      // useState不要、formDataから直接値を取得
      const email = formData.get('email');
      const password = formData.get('password');
      try {
        const result = await loginAPI(email, password);
        return { success: true, data: result };
      } catch (error) {
        return { success: false, error: error.message };
      }
    },
    { success: false, data: null, error: null } // 初期状態
  );

  return (
    <form action={submitAction}>
      <input name="email" type="email" />
      <input name="password" type="password" />
      {/* isPendingで自動管理、手動setLoading不要 */}
      <button disabled={isPending}>
        {isPending ? 'ログイン中...' : 'ログイン'}
      </button>
      {state.error && <p className="error">{state.error}</p>}
    </form>
  );
}

Before vs After
計算してみると、ログインフォームのコードは45行(状態管理、エラー処理、リセット含む)から30行未満に削減されました。さらに重要なのは、ロジックがクリアになったことです:

  • loadingsetLoading の手動管理が不要
  • エラー処理が戻り値に統合されている
  • isPending フラグが即使える
  • フォームデータは formData から直接取得可能、useState の山が不要

いつActionsを使うべき?
3つの典型的なシナリオがあります:

  1. フォーム送信:ログイン、登録、コメント投稿など、非同期検証と送信が必要な場合
  2. データ更新:カートの数量変更、いいね/お気に入り、状態切り替え
  3. マルチステップ操作:楽観的更新(Optimistic Update)が必要な場合、useOptimistic との相性が抜群

正直、最初は従来のイベントハンドラと競合しないか心配でしたが、実際は共存可能です。複雑なビジネスロジックは従来通り、フォーム周りはActionsと使い分けると非常に快適です。

use() Hook - 非同期データ取得の新しい作法

なぜ use() が必要なのか?
これまでコンポーネント内でデータを取得するには、こう書くのが定石でした:

// 従来の useEffect + useState パターン
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchUser(userId).then(data => {
      setUser(data);
      setLoading(false);
    });
  }, [userId]);

  if (loading) return <div>読み込み中...</div>;
  return <div>{user.name}</div>;
}

問題はありませんが、毎回loading状態管理を書くのは冗長です。

use() の魔法
React 19で導入された use() Hookは非常に興味深いです。なんと、条件分岐の中で呼び出せます(従来のHookのルールを打ち破っています!)。Suspenseと組み合わせると、コードは一瞬で簡潔になります:

import { use, Suspense } from 'react';

function UserProfile({ userId }) {
  // 注意!条件分岐内で use() を呼び出せる。従来のHookでは許されなかったことです。
  const userPromise = userId ? fetchUser(userId) : null;
  const user = userPromise ? use(userPromise) : null;

  if (!user) return <div>ユーザーを選択してください</div>;
  return <div>{user.name}</div>;
}

// 親コンポーネントでSuspenseでラップし、loading状態を一元管理
function App() {
  return (
    <Suspense fallback={<div>読み込み中...</div>}>
      <UserProfile userId={123} />
    </Suspense>
  );
}

決定的な違い
use()useEffect の最大の違いは、思考モデルの転換です:

  • useEffect:命令的。「データを取ってきた後、状態をセットしろ」
  • use():宣言的。「このコンポーネントは、このデータを必要としている」

半日試してみて分かったのは、use() はServer Componentsと併用すると特に強力で、クライアントコンポーネントでの非同期データ処理も非常に楽になるということです。

落とし穴
注意してください! use() は条件分岐で使えますが、いくつか制約があります:

  • renderフェーズでのみ呼び出し可能(イベントハンドラ内はNG)
  • Promiseは安定した参照である必要がある(useMemo でラップするなど)
  • エラー処理には Error Boundary が必要

私は最初、use(fetch(...)) とコンポーネント内に直接書いてしまい、レンダリングのたびにfetchが走って無限ループに陥りました。useMemo を忘れないでください。

React Compiler - 自動パフォーマンス最適化の魔法

memo を付け忘れる常習犯(あなたもですよね?)にとって、React Compilerは救世主です。

コンパイラは何をするのか?
簡単に言えば、ビルド時にコードを解析し、必要な場所に memouseMemouseCallback などの最適化コードを自動的に挿入してくれます。パフォーマンス最適化専属のアシスタントが隣にいるようなものです。

どれくらいコードを削れる?
中規模プロジェクトで試したところ、手動で書いていた30箇所以上の memouseMemo を削除しても、ページパフォーマンスは変わらないか、むしろ向上しました。Metaの公式データでも、手動最適化コードを大量に削減できることが示されています。

使い方は?
Babelプラグインを入れるだけです:

# Pluginインストール
npm install babel-plugin-react-compiler

.babelrc に設定を追加:

{
  "plugins": ["babel-plugin-react-compiler"]
}

コンパイラが助けてくれない場合
ぬか喜びは禁物です。万能ではありません:

  1. Reactのルール違反:render中で外部変数を変更するようなコードは最適化されません。
  2. 動的な依存関係:依存関係が動的すぎる場合、判断が難しくなります。
  3. ライブラリ互換性:一部の古いライブラリでは問題が起きる可能性があります。

私のアドバイス:小規模プロジェクトでは恩恵を感じにくいですが、中〜大規模プロジェクトでは明確なメリットがあります。本番投入前には十分なテストを。

Server Components とリソース管理

正直、Server Components (SC) はReact 19で最も理解が難しい部分でした。最初は公式ドキュメントを読んでも混乱しました。しかし、理解できると非常に強力な武器になります。

Server Componentsを5文で説明

  1. サーバー上で実行され、クライアントのJSバンドルには含まれない。
  2. APIを書かずに、直接DBやファイルシステムにアクセスできる。
  3. レンダリング結果は特殊な形式でクライアントに送られ、表示される。
  4. Client Components (CC) と混在可能だが、明確な境界線がある。
  5. 主にデータ表示用。ユーザー操作(クリックなど)は扱えない(それはCCの仕事)。

ドキュメントメタデータの新管理法
以前、Next.jsでSEOメタデータを管理するのは面倒でした。React 19では、コンポーネント内に直接 <title><meta> を書くと、Reactが自動的に <head> へ持ち上げてくれます:

// Server Component - SEOタグを直書き
function BlogPost({ post }) {
  return (
    <>
      {/* これらは自動的に <head> に移動する */}
      <title>{post.title} - My Blog</title>
      <meta name="description" content={post.summary} />
      <meta property="og:image" content={post.coverImage} />
      
      <article>
        <h1>{post.title}</h1>
        <p>{post.content}</p>
      </article>
    </>
  );
}

これは便利すぎます!深くネストされたコンポーネントからでもページタイトルを制御できます。

リソースプリロードの最適化
スタイルシートの優先順位制御もサポートされました:

// 高優先度 - クリティカルCSS
<link rel="stylesheet" href="/critical.css" precedence="high" />
// 低優先度 - 遅延読み込み
<link rel="stylesheet" href="/optional.css" precedence="low" />

これでFOUC(スタイル適用前のちらつき)を防げます。

SCとCC、どう使い分ける?
私の判断基準:

  • Server Components:データ表示、SEO重要ページ、バックエンドリソースへのアクセスが必要
  • Client Componentsuse client):インタラクション、ブラウザAPI使用、状態管理が必要
    混合する場合は、SCを外枠にし、インタラクションが必要な部分だけCCとして埋め込みます。

最大の罠
use client の境界線です。親に use client を書くと、その子孫もすべてクライアントコンポーネントになります。境界線はできるだけ末端(ボタンなど)に設定し、サーバコンポーネントの利点を消さないようにしましょう。

refクリーンアップとWeb Componentsサポート

地味ですが、特定の場面で役立つ機能です。

refコールバックのクリーンアップ
以前はrefでDOMイベントをリッスンするとクリーンアップを忘れがちで、メモリリークの原因になりました。React 19ではrefコールバックからクリーンアップ関数を返せます:

<div ref={(node) => {
  if (node) {
    // 監視ロジック
    const observer = new IntersectionObserver(() => { ... });
    observer.observe(node);
    // クリーンアップ関数 - アンマウント時に実行
    return () => {
      observer.disconnect();
    };
  }
}} />

useEffect に似たパターンで、サードパーティライブラリ(チャートや地図など)の統合に便利です。

Web Componentsの完全サポート
Custom ElementsとWeb Components APIを完全サポートしました。

function App() {
  return <my-custom-element data={someData} />;
}

企業独自のデザインシステムや、フレームワークに依存しないコンポーネント資産がある場合、これは大きな価値があります。

アップグレードガイドと注意点

良いことばかり言いましたが、アップグレードすべきでしょうか?私の評価プロセスを共有します。

破壊的変更リスト
注意すべきBreaking Changes:

  1. propTypes 削除:TypeScriptへ移行するか削除しましょう。
  2. defaultProps(関数コンポーネント)削除:ES6のデフォルト引数を使いましょう。
  3. Legacy Context 削除:新しいContext APIへ移行必須。
  4. 文字列refs 削除:callback refs か createRef へ。
    これらは長らく非推奨だったものなので、まともにメンテしていれば問題ないはずです。

段階的アップグレード戦略
私のアドバイス:

  • 小規模(<50コンポーネント):一気に上げてOK。問題箇所の特定も容易。
  • 中規模(50-200):開発ブランチでテスト。フォームやリスト表示などコア機能を重点チェック。
  • 大規模(>200)
    1. 非コア機能でパイロット運用
    2. パフォーマンス指標を監視しながら徐々に移行
    3. 2025年Q1まで待つのも手(エコシステムの成熟待ち)

パフォーマンステスト
アップグレード後は必ず比較計測しましょう:

  • 初期描画時間(FCP)
  • インタラクション応答速度(INP)
  • バンドルサイズ
  • メモリ使用量
    私の個人PJでは、Compiler有効化で初期描画が約150ms高速化しました。バンドルサイズはほぼ変わらず(ビルド時最適化なので)。

エコシステム対応状況
主要ライブラリは既に対応済みです:

  • Next.js 15 全面対応
  • Redux Toolkit, React Router v7 対応
  • Ant Design, MUI 順次対応中
    マイナーなライブラリを使っている場合は、GitHubで互換性を確認してください。

まとめ

冒頭の金曜日の午後にもしReact 19を使えていたら、あのログインフォームは15行で済み、useState の山に埋もれることもなかったでしょう。

React 19は革命ではありませんが、「現場の痛み」に効くアップデートです:

  • Actions:フォーム処理をシンプルに
  • use() Hook:データ取得を宣言的に
  • React Compiler:パフォーマンス最適化を自動化
  • Server Components:SEOとパフォーマンスの課題解決
  • refクリーンアップ & Web Components:エコシステムの穴埋め

React 15時代からの古参ユーザーとして、今回の更新には期待しています。私はまず個人プロジェクトで全機能を試し、知見を溜めてから、2025年Q1以降に業務プロジェクトへの導入検討を始める予定です。

あなたのネクストステップ

  1. 試す:新規プロジェクトでReact 19を採用し、新機能を体験する。
  2. 学ぶReact公式ブログ を読む(新ドキュメントは優秀)。
  3. 参加する:DiscordやGitHubで最新情報をキャッチアップ。
  4. 共有する:アップグレード体験やトラブルを共有し合う。

あなたはReact 19を試しましたか?ActionsとCompiler、どっちに魅力を感じますか?コメントで語りましょう!

React 19 コア機能実践ガイド

Actionsによるフォーム処理からServer Componentsまで、完全な使用手順と最適化戦略

⏱️ Estimated time: 2 hr

  1. 1

    Step1: Actionsでフォーム処理を簡素化

    useActionStateをインポート:
    import { useActionState } from 'react'

    Actions関数を定義:
    const [state, submitAction, isPending] = useActionState(
    async (prevState, formData) => {
    const email = formData.get('email');
    try {
    const result = await loginAPI(email);
    return { success: true, data: result };
    } catch (error) {
    return { success: false, error: error.message };
    }
    },
    { success: false, data: null, error: null }
    )

    フォームで使用:
    <form action={submitAction}>
    <input name="email" />
    <button disabled={isPending}>ログイン</button>
    {state.error && <p>{state.error}</p>}
    </form>

    メリット:
    • pending/error/success状態を自動管理
    • loadingの手動制御不要
    • useState地獄からの解放
  2. 2

    Step2: use() Hookで非同期データを取得

    useとSuspenseを使用:

    const userPromise = userId ? fetchUser(userId) : null;
    // 条件分岐内での使用が可能!
    const user = userPromise ? use(userPromise) : null;

    親コンポーネント:
    <Suspense fallback={<div>Loading...</div>}>
    <UserProfile userId={123} />
    </Suspense>

    違い:従来のuseEffect(命令的)に対し、use()は宣言的(データ要求)。
  3. 3

    Step3: React Compilerで自動最適化

    インストール:
    npm install babel-plugin-react-compiler

    設定(.babelrc):
    { "plugins": ["babel-plugin-react-compiler"] }

    効果:
    • ビルド時にコードを解析
    • memo/useMemo/useCallbackを自動挿入
    • 手動最適化コードを削除してもパフォーマンス維持・向上
    • 中〜大規模プロジェクトで特に有効
  4. 4

    Step4: Server Componentsとリソース管理

    Server Components (SC):
    • サーバー実行、バンドルサイズ削減
    • DB/ファイルシステムへの直接アクセス
    • <title>, <meta> タグの自動hoisting(headへの移動)

    使い分け:
    • SC:データ表示、SEO、バックエンドアクセス
    • Client Components ('use client'):インタラクション、ブラウザAPI、State管理

    注意:'use client' の境界線は子孫全てに影響するため、慎重に設定。
  5. 5

    Step5: refクリーンアップとWeb Components

    refクリーンアップ:
    <div ref={(node) => {
    const observer = ...;
    // クリーンアップ関数を返却可能
    return () => observer.disconnect();
    }} />

    Web Components:
    <my-custom-element /> を完全サポート。デザインシステムの統合が容易に。
  6. 6

    Step6: アップグレード戦略

    破壊的変更の確認:
    • propTypes, defaultProps, Legacy Context, 文字列refsの廃止

    移行プラン:
    • 小型:即移行
    • 中型:開発ブランチでコア機能テスト
    • 大型:非コア機能から段階的導入、2025 Q1まで待つのも手

    検証項目:FCP、INP、バンドルサイズ、メモリ

FAQ

React 19のActionsとは?useActionStateはどう使う?
Actionsは非同期操作を処理する新機能です。useActionStateを使うと、非同期関数をフォームに直接渡し、pending/error/success状態をReactに自動管理させることができます。これにより、手動でのloading管理や大量のuseStateが不要になり、フォームコードが劇的に簡潔になります。
use() HookはuseEffectとどう違うの?
use()は条件分岐内で呼び出せる点が革新的です(従来のHookルール外)。useEffectが「副作用としてデータを取得し状態をセットする(命令的)」のに対し、use()は「このデータが必要である(宣言的)」と定義します。Suspenseと組み合わせることで、非同期データ読み込みコードが非常にシンプルになります。
React Compilerを導入するメリットは?
ビルド時に自動的に `memo`、`useMemo`、`useCallback` を適切な場所に挿入してくれます。開発者が手動でパフォーマンス最適化を気にする必要がなくなり、コードの可読性が上がると同時に、アプリケーションのパフォーマンスが維持・向上します。
Server ComponentsとClient Componentsの使い分けは?
Server Componentsは「データ取得・表示」「SEO重視」「バックエンドアクセス」に使用します。Client Components('use client')は「クリックなどのユーザー操作」「ブラウザAPI(window/localStorage)」「useStateなどの状態管理」が必要な場合に使います。基本はServer Componentsで構成し、対話的な部分だけClient Componentsにするのが定石です。
React 19へのアップグレードで注意すべき破壊的変更は?
propTypes、defaultProps(関数コンポーネント)、Legacy Context、文字列refsなどが削除されました。これら古いAPIを使用している場合は、TypeScriptや新しいContext API、callback refsへの移行が必要です。
refコールバックのクリーンアップとは?
refコールバック関数からクリーンアップ関数(関数が戻り値)を返せるようになりました。これにより、IntersectionObserverなどのDOM監視を行う際、コンポーネントのアンマウント時に自動的に監視を解除する処理が、useEffectを使わずにref内だけで完結して書けるようになります。

7 min read · 公開日: 2025年11月23日 · 更新日: 2026年1月22日

コメント

GitHubアカウントでログインしてコメントできます

関連記事