SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

フロントエンド開発における定番ライブラリ「React」の最新バージョン解説

フロントエンドの定番ライブラリ「React」バージョン19の新機能を紹介──アクションによる非同期処理の進化

フロントエンド開発における定番ライブラリ「React」の最新バージョン解説 第3回


  • X ポスト
  • このエントリーをはてなブックマークに追加

React 19の「アクション」による非同期データ更新の簡略化

 ここまで、React 19のトランジションで「非同期なデータ取得」処理を簡潔に記述できることを確認しました。ここからはReact 19によって「非同期なデータ更新」の実装がどう変わるかにフォーカスします。

 まず、React 19で新たに導入された「アクション」という概念を紹介します。アクションは非同期処理をより簡単に管理できる仕組みです。公式情報では「非同期トランジションを使用する関数を規約として『アクション』と呼ぶ」と説明されています。

 従来のReactでは、データの送信や非同期処理の状態管理は各コンポーネントの内部で個別にハンドリングする必要があり、複雑なコードになることもありました。今後はアクションを用いることで、非同期処理における状態やエラーハンドリング、成功・失敗のフィードバック表示などを簡潔に管理できます。

[コラム]「アクション」と「非同期関数」は同じか

 端的には「アクション=非同期関数」という理解でもよさそうですが、「非同期トランジションを使用する関数」と表現されていることがミソです。つまり、「トランジション」の特徴を受け継いだ非同期関数が「アクション」だということになります。

 具体的には、トランジションで提供されていた「送信中状態の管理」と「応答性の高いレンダリング」という特徴を非同期関数でも利用できるようにしたものと言えます。これに加え、フォームから直接的に呼び出しやすくなり、「楽観的更新」を使ったユーザー体験の向上が可能になっています。

 まず、従来のReactでは、データの更新処理をどのように書いていたかを見てみます。ここでは「タイトル」を更新する簡単なフォームを考えます。

[リスト3]Reactを使ったシンプルなデータ更新処理の例(03_react18-data-update/src/index.js)
function UpdateTitle({ value, onUpdate }) {
  const [error, setError] = useState(null); // エラー情報
  const [isPending, setIsPending] = useState(false); // 処理中フラグ

  const handleSubmit = async (event) => {
    event.preventDefault();
    // 処理中フラグを立てる
    setIsPending(true);
    // フォームの入力値で更新処理を実行
    const title = event.target.title.value;
    const { data, error } = await updateTitle(title);
    // 処理中フラグを倒す
    setIsPending(false);
    if (error) {
      // エラーの場合はエラー情報を表示
      setError(error);
      return;
    }
    onUpdate(data);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" defaultValue={value} disabled={isPending} />
      <button type="submit" disabled={isPending}>
        更新
      </button>
      {error && <p>{error}</p>}
      <div>value: {value}</div>
    </form>
  );
}

 なお、このUpdateTitleコンポーネントはリスト4のように親コンポーネントから呼び出されているものとします。

[リスト4]UpdateTitleコンポーネントを利用するApp(03_react18-data-update/src/index.js)
function App() {
  const [title, setTitle] = useState("React はじめました");
  return <UpdateTitle value={title} onUpdate={setTitle} />;
}

 また、ここでupdateTitle関数はリスト5のようになんらかの更新処理を行って、title(エラーの場合はerror)を返す処理だとします。ダミーのためsetTimeoutで1秒間のウェイトを入れています。

[リスト5]ダミーのデータ更新関数(03_react18-data-update/src/index.js)
function updateTitle(value) {
  return new Promise((resolve) =>
    // なんらかの更新処理
    setTimeout(() => {
      resolve({ data: value, error: null }); // 成功時
      // resolve({ error: "更新に失敗しました" }); // 失敗時
    }, 1000)
  );
}
Reactを使ったシンプルなデータ更新処理の例
Reactを使ったシンプルなデータ更新処理の例

 以後、このUpdateTitleコンポーネントをベースに変更を加えていきます。

useActionStateフックと<form>アクション:非同期のフォーム送信処理をシンプルに

 useActionStateは、「アクション」の結果に基づいて状態管理を行うために、React 19で導入された新しいフックです。React 19の新APIの中でも特に幅広い用途を持っており重要です。

 またReact 19では<form>に「アクション」を直接指定できるようになり、より簡潔に「フォームによる送信」を制御できるようになりました。Reactの公式ドキュメントでもuseActionState<form>アクションと組み合わせて使用することが前提になっています。

 早速、UpdateTitleコンポーネントをuseActionState<form>アクションを使って書き換えてみます。

[リスト6]useActionStateを使ったデータ更新処理(06_react19-data-update-useactionstate/src/index.js)
function UpdateTitle({ value, onUpdate }) {
  // useActionStateの戻り値はステート、アクション、送信中状態フラグ ... (1)
  const [error, submitAction, isPending] = useActionState(
    // 非同期関数をアクション本体として渡す ... (2)
    async (previousState, formData) => {
      // 第1引数: 変更前のステート, 第2引数: `FormData` ... (3)
      const value = formData.get("title");
      const { data, error } = await updateTitle(value);
      if (!error) {
        onUpdate(data);
      }
      return error;
    },
    null // ステートの初期値
  );

  return (
    // formのactionにアクションを直接指定できる ... (4)
    <form action={submitAction}>
      <input name="title" defaultValue={value} disabled={isPending} />
      <button type="submit" disabled={isPending}>
        更新
      </button>
      {error && <p>{error}</p>}
      <div>value: {value}</div>
    </form>
  );
}

 動作自体はuseTransitionの場合と変わっていませんが、注目すべきは以下の点です。

(1)useActionStateの戻り値はstate、アクション、送信中状態フラグ

 useStateはもはや一つもなくなり、新しいuseActionStateフックだけになりました。これまで扱ってきたerrorという「ステート」と、「送信中状態」(isPending)のいずれも、useActionStateフックから取得しています。

 ここで、useActionStateの2番目の戻り値が「アクション」であることを意識しましょう。このアクションは前述の通り「非同期トランジション」の性質をもっているので、内部で実行されるステート更新は「優先度の低い更新」として扱われます。

 なお、ここでは「ステート」としてエラー文字列を管理していますが、ここで管理するステートはsetStateと同様にオブジェクトなどの値でも問題ありません。

(2)useActionStateにはアクションの本体となる非同期関数を渡す

 useActionStateの第1引数は、処理を実行する非同期関数です。

 ここで「非同期関数」を渡していることが特徴です。useTransitionの場合と同様に非同期関数を渡すことで、Promiseが解決するまでisPendingtrueに保たれるため、「送信中」であることを簡潔に管理できます。

 第2引数にはuseStateと同様に、state の初期値を指定します。

(3)アクションは第1引数に変更前のステート、第2引数にFormDataを受け取る

 アクションとして指定する非同期関数の引数が「変更前のステート」と「FormData」になっていることに注目しましょう。

 「変更前のステート」が得られるのはsetStateと似ています。ステートの値を前の値に基づいて変更する場合のために、「前のステート」へアクセスする手段が提供されています。

 次に第2引数は<form>アクションから渡されるもので、共通Web APIのFormDataインターフェースになっています。よって、フォームのデータに対してFormData.get("属性名")でアクセスできます。DOM要素の値を参照する必要はなくなりました。

[Note]「前のステート」へのアクセス

 「前のステート」にアクセスする必要があるのは、更新後の値が更新前の値に依存している場合です。典型的なユースケースとしてユーザーが「いいね」を押すたびにカウントを増やすような場合が挙げられます。

[リスト7]前のステートを利用する例(1)
const [count, likeAction, isPending] = useActionState(
  async (prevCount) => {
    await incrementLike(value);
    return prevCount + 1;
  }
);

 あるいはステートがオブジェクトや配列の場合に、一部のプロパティや要素だけを更新する場合も同様です。

[リスト8]前のステートを利用する例(2)
const [user, updateUserNameAction, isPending] = useActionState(
  async (prevUser, formData) => {
    const name = formData.get("name");
    await updateUser(name);
    return { ...prevUser, name };
  }
);

(4)<form>actionにアクションを直接渡している

 useActionStateフックの戻り値の「アクション」をそのまま<form>タグのaction属性に直接渡しています。これが「<form>アクション」の機能です。

 前述のとおり、<form>アクションを利用すると、FormDataインターフェースのオブジェクトが関数の引数として受け取れます。

まとめ

 今回は、React 19で導入されたuseActionState<form>アクションによって、非同期データ更新処理の実装が大幅に簡略化されました。これにより、開発効率が向上すると同時に、コードの可読性も改善される点が大きな魅力です。

 次回はReact 19の「アクション」関連の付加的なAPIのほか、その他の新機能や変更点について紹介していきますので、ぜひご期待ください。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
フロントエンド開発における定番ライブラリ「React」の最新バージョン解説連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 山田 研二(ヤマダ ケンジ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/20623 2024/12/18 13:13

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング