🫛

100秒テックの「React 19 がでた」を見たので、チートシートを作成してみました

2025/01/18に公開

はじめに

今回は、いつも楽しく拝見させていただいている「100秒テック」akiさんとコラボしました!
まずは、とにかく動画をご覧ください!

https://www.youtube.com/watch?v=ZzHJ99mpEow

他にも役立つフロントエンド関連の技術動画がたくさんあるので、いいねとチャンネル登録をよろしくね!(言いたかったw)

この記事では、タイトル通り、React19のチートシートとして活用いただけるように仕上げました。またサンプルコードは、できるだけ理解しやすいようシンプルにして、React環境にコピー&ペーストするだけで動作するようにしました。
動画と合わせて、この記事もReact 19の理解を深めるのに役立てていただけると嬉しいです。ぜひ、最後までご覧ください!

アクション

アクションは、React 19を理解する上で欠かせない重要な概念です。このアクションを基盤として、様々な機能が追加されています。そのため、React 19で追加された機能を活用するにあたっては、アクションを理解しておくことをおすすめします。この章では、アクションの仕組みと背景について説明します。

公式ドキュメントでは、アクションを次のように定義しています。

非同期トランジションを使用する関数を規約として「アクション」と呼ぶ

ではまず、Reactにおけるトランジションとは何でしょうか?
トランジションというと、フロントエンド開発者であれば、CSSのtransitionやView Transitions APIを想像してしまうかもしれませんが、それらとは異なります。
これはReact 18で導入された概念です。

これも公式ドキュメントによると、次のように定義しています。

トランジション(transition; 段階的推移)とは React における新たな概念であり、緊急性の高い更新 (urgent update) と高くない更新 (non-urgent update) を区別するためのものです。
緊急性の高い更新とはタイプ、クリック、プレスといったユーザ操作を直接反映するものです。
トランジションによる更新は UI をある画面から別の画面に段階的に遷移させるものです。
タイプ、クリック、プレスのような緊急性の高い更新は、物理的な物体の挙動に関する我々の直観に反しないよう、即座に反応する必要があり、そうでないと「おかしい」と認識されてしまいます。一方でトランジション内では、ユーザは画面上であらゆる中間の値が見えることを期待していません。

Reactにおいてトランジションとは、ユーザー体験をスムーズに保ちながら、非同期処理やUI更新を行うための仕組みです。トランジションを使うと、Reactは「何をすぐに更新するか(緊急)」と「何を後で更新するか(優先度が低い)」を分けて処理できます。

この概念を具現化したものがuseTransitionフックやstartTransitionAPIです。

useTransitionフック
const [isPending, startTransition] = useTransition();

const handleSubmit = () => {
  startTransition(() => {
    // ステート更新処理を行う
    setSomeState();
  });
}

このstartTransitionのコールバック内で行われたステート更新はトランジションとなり、優先度の低い更新として扱われます。

React 18ではこのコールバック関数は同期関数しか扱えませんでしたが、React 19で非同期関数も扱えるようになり、これをアクションと定義した。ということです。

React19の非同期トランジション
const [isPending, startTransition] = useTransition();

const handleSubmit = async () => {
    startTransition(async () => { // React 19で非同期関数の使用が可能になった
      // 非同期のステート更新処理を行う
      await someAsyncOperation();
    });
};

useActionState フック

概要

useActionState は、アクションの実行 (useTransition のような機能) と、実行結果に応じた状態管理 (useReducer のような機能) を1つのフックで実現するためのものです。

アクション実行のみが必要な場合は useTransition で十分ですが、アクションの結果に基づいて状態を更新したい場合useActionState が役立ちます。

シグネチャ

const [state, formAction, isPending] = useActionState(action, initialState, parmalink?);

引数

引数 説明
action 実行するアクション
initialState Stateの初期値
parmalink (optional) フォームアクションの結果に基づいて遷移するURLを指定

戻り値

戻り値 説明
state 現在のState
formAction アクションを実行するための関数
isPending トランジションが保留中であるかどうかを示すフラグ

サンプルコード

import { useActionState } from "react";

const incrementLike = async (currentCount: number): Promise<number> => {
  return await new Promise((resolve) =>
    setTimeout(() => resolve(currentCount + 1), 1000)
  );
};

export const LikeCounter: React.FC = () => {
  const [likeCount, likeCountAction, isPending] = useActionState(
    incrementLike,
    0
  );

  return (
    <form>
      <div>{isPending ? "Updating…" : likeCount}</div>
      <button formAction={likeCountAction}>Like</button>
    </form>
  );
};

補足: useActionStateuseState+useTransitionの挙動の違いについて

ちなみにですが、上記サンプルコードをuseActionStateを使わずに実装しようとすると、以下のようにuseStateuseTransitionを使って実現する方法が考えられます。

useActionStateを使わないサンプルコード
import { useTransition, useState } from "react";

const incrementLike = async (currentCount: number): Promise<number> => {
  return await new Promise((resolve) =>
    setTimeout(() => resolve(currentCount + 1), 1000)
  );
};

export const LikeCounter: React.FC = () => {
  const [likeCount, setCount] = useState(0);
  const [isPending, startTransition] = useTransition();

  const likeCountAction = () => {
    startTransition(async () => {
      const updatedLikeCount = await incrementLike(likeCount);
      setCount(updatedLikeCount);
    });
  };

  return (
    <form>
      <div>{isPending ? "Updating…" : likeCount}</div>
      <button formAction={likeCountAction}>Like</button>
    </form>
  );
};

実行してみるとわかるのですが、同じ挙動にはなりません。

useActionStateを使用した場合、ボタンを連打しても、クリックした回数だけ非同期処理が実行され、各処理の結果が順番に反映されます。

しかし、useStateuseTransitionを組み合わせて実装した場合、likeCountをこのコンポーネントで管理しているので、ボタンを連打すると、トランジションの実行結果を反映するわけではなく、あくまで現在の状態を基に更新しようとします。クリックは連打した回数分実行されますが、同一トランジション内では、同じ結果が返ってきます。そのためクリックが防がれているような挙動に見えます。

同じような処理をuseStateuseTransitionで実装しようとすると、かなり複雑な実装になります。

つまり、useActionStateは、非同期処理を伴う状態更新をより安全かつ適切に行うためのフックなのです。

公式ドキュメント

https://ja.react.dev/reference/react/useActionState

<form> アクション

概要

react-domの<form>のaction属性にアクションが渡せるようになりました。これによりフォーム送信時にアクションを実行できるようになりました。

使い方

<form>のaction属性にアクションを指定します。

<form action={runAction}></form>

サンプルコード

import { useState } from "react";

export const ContactForm: React.FC = () => {
  const [name, setName] = useState("");

  async function handleSubmit(formData: FormData) {
    const name = formData.get("name");
    return `Hello, ${name}!`;
  }

  return (
    <form
      action={async (formData) => {
        const result = await handleSubmit(formData);
        setName("");
        alert(result);
      }}
    >
      <div>
        <label htmlFor="name">Name:</label>
        <input
          type="text"
          id="name"
          name="name"
          value={name}
          placeholder="Your name"
          onChange={(e) => setName(e.target.value)}
        />
      </div>

      <button type="submit">Send</button>
    </form>
  );
};

公式ドキュメント

https://ja.react.dev/reference/react-dom/components/form

useFormStatus フック

概要

現在のフォーム送信に関するステータス情報を提供するフックです。このフックを使用することで、コンポーネント間でPropsを介して状態を受け渡すProps Drilling、Propsのバケツリレーをせずにステータス情報を取得することができます。

使い方

import {useFormStatus} from 'react-dom';

function MyButton() {
  const {pending} = useFormStatus();
  return <button type="submit" disabled={pending} />
}

引数
なし

戻り値

戻り値 説明
pending 親Formで送信が進行中かを判定するフラグ
data FormData インターフェイスのオブジェクト
method 'get' or 'post'かのいずれかの文字列。HTTPメソッドのどちらで送信しているかを表す
action 親Formのpropsであるactionに渡された関数の参照

サンプルコード

import { useTransition, useState } from "react";
import { useFormStatus } from "react-dom";

const incrementLike = async (currentCount: number): Promise<number> => {
  return await new Promise((resolve) =>
    setTimeout(() => resolve(currentCount + 1), 1000)
  );
};

const LikeButton = () => {
  const { pending } = useFormStatus();
  return <button disabled={pending}>{pending ? "Processing…" : "Like"}</button>;
};

export const LikeCounter: React.FC = () => {
  const [likeCount, setCount] = useState(0);
  const [, startTransition] = useTransition();

  const likeCountAction = async () => {
    startTransition(async () => {
      const updatedLikeCount = await incrementLike(likeCount);
      setCount(updatedLikeCount);
    });
  };

  return (
    <form action={likeCountAction}>
      <div>{likeCount}</div>
      <LikeButton />
    </form>
  );
};

公式ドキュメント

https://ja.react.dev/reference/react-dom/hooks/useFormStatus

useOptimistic フック

概要

UIを楽観的に更新するためのフックです。
サーバーのレスポンスを待たずに入力内容を即座に UI に反映させることができます。

シグネチャ

const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);

引数

引数 説明
state 初期状態や、実行中のアクションが存在しない場合に返される値です
updateFn(currentState, optimisticValue) currentStateは現在のステート値です。optimisiticValueはaddOptimisticに渡された楽観的更新に使用する値です。updateFnはこれらの値を使用して、新しい楽観的ステートを返します。

戻り値

戻り値 説明
optimisticState 結果としての楽観的ステートです。実行中のアクションがない場合は ステートと等しくなり、何らかのアクションが実行中の場合はupdateFnが返す値と等しくなります。
addOptimistic 楽観的な更新を行う際に呼び出すためのディスパッチ関数です。任意の型の引数のoptimisticValueを受け取ります。stateとoptimisticValueを引数にしてupdateFnが呼び出されステートが更新されます。

サンプルコード

import { useOptimistic, useState, useTransition } from "react";

const incrementLike = async (currentCount: number): Promise<number> => {
  return new Promise((resolve) =>
    setTimeout(() => resolve(currentCount + 1), 1000)
  );
};

export const LikeCounter: React.FC = () => {
  const [likeCount, setCount] = useState(0);

  const [optimisticLikeCount, incrementLikeOptimistic] = useOptimistic(
    likeCount,
    (currentValue: number, optimisticValue: number) =>
      currentValue + optimisticValue
  );

  const [isPending, startTransition] = useTransition();

  const handleClick = () => {
    startTransition(async () => {
      incrementLikeOptimistic(1); // 楽観的に +1 する
      const updatedLikeCount = await incrementLike(likeCount); // サーバーから値を取得
      setCount(updatedLikeCount); // サーバからの値を state に反映
    });
  };

  return (
    <div>
      <div>{optimisticLikeCount}</div>
      <button disabled={isPending} onClick={handleClick}>
        Like
      </button>
    </div>
  );
};

公式ドキュメント

https://react.dev/reference/react/useOptimistic

use API

概要

use APIは、プロミスやコンテキストなどのリソースから値を読み取るためのAPIです。
サーバーコンポーネントとクライアントコンポーネントで使用する際には、以下の注意点を参考にしてください。

シグネチャ

const value = use(resource);

引数

引数 説明
resource 値を読み取りたいプロミスまたはコンテキストを指定します。

戻り値

戻り値 説明
value プロミスで解決された値やコンテキストの値が返ってきます。

サンプルコード

補足: useを使わずにuseEffectで実現した場合

useEffectを使った場合は、useStateも必要になりますし、コード量が多くなりますね。

import { useEffect, useState } from "react";

const fetchData = async () => {
  return new Promise<string>((resolve) => {
    setTimeout(() => {
      resolve("Hello from use!");
    }, 1000);
  });
};

export default function Example() {
  const [data, setData] = useState<string | null>(null);

  useEffect(() => {
    fetchData().then((result) => {
      setData(result);
    });
  }, []);

  return (
    <div>
      <h2>use のサンプル</h2>
      <p>データ: {data}</p>
    </div>
  );
}

公式ドキュメント

https://react.dev/reference/react/use

React Server Components(RSC)

概要

React Server Component (RSC)は、Reactの新しい機能で、サーバーサイドでレンダリングされるコンポーネントを作成できるものです。これにより、フロントエンドでのJavaScriptバンドルを最小限に抑え、サーバーサイドでのデータ取得や処理を効率的に行うことができます。

RSCの内容は非常に深いため、ここでは基本的な説明にとどめておきます。さらに詳しく知りたい方は、公式ドキュメントやその他のリソースを参照することをおすすめします。

サンプルコード

export default async function PostsPage() {
  // 外部APIから記事データを取得する
  const response = await fetch("https://example.com/posts");
  // レスポンスがOKでなければエラーをthrow
  if (!response.ok) {
    throw new Error(`API request failed with status ${response.status}`);
  }
  // JSON形式でレスポンスを解析
  const postDataList = await response.json();

  return (
    <div>
      <h2>記事一覧</h2>
      <ul>
        {/* 取得した記事データをリスト表示 */}
        {postDataList.map((post) => (
          <li key={post.id}>
            <a href={post.url}>{post.title}</a>
          </li>
        ))}
      </ul>
    </div>
  );
}

公式ドキュメント

https://ja.react.dev/reference/rsc/server-components

Server Actions

概要

Server ActionsはReactのクライアントコンポーネントから直接サーバーサイドのロジックを実行できる仕組みです。これにより、クライアントとサーバー間の通信が簡潔になり、アプリケーション全体のコードの一貫性が向上します。
クライアントから見たら、非同期関数であり、アクションの一種と考えることができます。

サンプルコード

公式ドキュメント

https://ja.react.dev/reference/rsc/server-actions

おわりに

今回は、React 19の改善点については詳しく触れませんでしたが、興味深い変更がいくつもありました。
例えば、refにpropsとしてアクセスできるようになったり、ref用のクリーンアップ関数が用意されたり、Contextのプロバイダーが従来の <Context.Provider> に加えて、<Context> だけで使用できるようになった点などが挙げられます。<Context.Provider>は非推奨になる予定です。

その他にも多くの改善点がありますので、お時間のあるときにぜひ確認してみてください。

ここまで読んでいただき、ありがとうございました🙏🏻🙏

あとakiさん、コラボ企画を許諾していただきありがとうございました!
また交流しましょうね🫡

https://ja.react.dev/blog/2024/12/05/react-19

Discussion