5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【React Server Components】公式ドキュメントから学ぶ 'use server' とサーバ関数の基礎

5
Posted at

はじめに

RSC(React Server Components)について学ぶシリーズです。
今回は、'use server'サーバ関数(旧称:サーバアクション)の基礎について学びます。

前回までの記事はこちら:

※次回「サーバ関数 × フォーム連携」編へつづく...

学習目標

この回を終えると、次のことができるようになります。

  • サーバ関数(Server Functions)の役割を説明できる
  • 'use server' を使ってサーバ関数を定義できる
  • クライアントコンポーネント(Client Components)からサーバ関数を呼び出せる
  • サーバ関数の引数や戻り値の制約を理解できる
  • セキュリティ上の注意点を説明できる

サーバ関数とは何か?

React では、クライアントからサーバ上の関数を直接呼び出せる仕組みがあります。

これが サーバ関数(Server Functions)です。

React 19 より前はサーバアクション(Server Actions)と呼ばれていました。

従来のデータ更新

たとえば「ユーザー名を保存する」場合、

フォーム送信やデータ更新のために、別途 API エンドポイントを作成する必要がありました。

開発者は、クライアントから API Route を呼び出し、その先でデータベース更新を行います。

await fetch("/api/user", { method: "POST", body: JSON.stringify(data) });

サーバ関数を使う場合

サーバ関数を使うと、React が自動的にサーバ関数への参照を作成し、それをクライアントコンポーネントへ渡します。

開発者は、通常の非同期関数と同様にサーバ側の処理を呼び出せます。

await saveUser(data)

と書くと、まるでクライアント上で関数が実行されているように見えますが、この呼び出しの裏側では、React が内部でネットワーク通信を行っています。
実際には React がサーバへリクエストを送信し、サーバ上で関数を実行した結果を返しています。

サーバ関数はネットワーク越しに呼び出されるため、必ず Promise を返す必要があります。
そのため、関数内で await を利用しない場合でも async 関数として定義しなければなりません。

なお、サーバ関数は更新目的のために設計されており、返り値がキャッシュされないため、データの取得には推奨されません。データの取得はサーバコンポーネント内で直接行うのが基本です。

サーバ関数の特徴

  • クライアントからサーバ側の非同期関数を呼び出せる仕組み
  • サーバ関数は async 関数として定義する必要がある
  • ネットワーク通信は React が内部で自動的に処理する
  • 呼び出し側は通常の関数のように利用できる
  • 更新目的のために設計されている

サーバ関数とサーバコンポーネントの違い

サーバ関数とサーバコンポーネントは名前が似ていますが、役割が異なります。

サーバ関数 サーバコンポーネント
役割 更新処理を実行する UIを生成する
呼び出し元 クライアントやフォームから呼び出される Reactのレンダリング時に実行される
代表的な用途 保存・更新・削除 データ取得・画面生成
戻り値 シリアライズ可能な値 JSX

サーバ関数は「処理を実行するもの」、サーバコンポーネントは「画面を返すもの」と考えるのがわかりやすいです。

'use server' とは?

'use server' は、関数をサーバ関数として定義するためのディレクティブです。

さきほど述べたとおり、React はこの関数への参照をクライアントへ渡し、呼び出された際にサーバ上で実行される仕組みを構築します。

サーバ関数の定義方法

関数レベル

個別の関数だけをサーバ関数にしたい場合、関数の先頭に 'use server' ディレクティブを記述します。

なお、関数レベルの 'use server' は サーバコンポーネント内でのみ定義できます(クライアントコンポーネントのファイル内では定義不可)。

async function saveUser(user) {
  'use server';

  // DB保存
}

モジュールレベル

複数の関数をまとめてサーバ関数にしたい場合、ファイルの先頭に 'use server' ディレクティブを記述します。
すると、そのファイルから export される関数がサーバ関数になります。

user.js
'use server';

export async function createUser() {
  // ...
}

export async function deleteUser() {
  // ...
}

サーバ関数の定義方法

  • 関数レベル:関数の先頭に 'use server' を記述する
  • モジュールレベル:ファイル先頭に 'use server' を記述する

どちらを使う?
関数レベルは、そのコンポーネント内だけで利用するサーバ関数を定義したい場合に向いています。

一方、モジュールレベルは、

  • 関連する処理をまとめやすい
  • 複数のコンポーネントから再利用しやすい
  • サーバ処理と UI を分離しやすい

といったメリットがあります。

そのため、規模が大きくなるほどモジュールレベルのほうが管理しやすくなります。

クライアントコンポーネントから呼び出す

サーバ関数はクライアントコンポーネントから呼び出すことができます。
呼び出し方には2通りあり、サーバコンポーネントで作成して props として渡す方法と、クライアントコンポーネントから直接インポートする方法があります。

今回は直接インポートする方法を扱います。
サーバコンポーネントから props として渡して利用するパターンや、フォームの action と組み合わせるパターンは次回のフォーム連携で見ていきます。

つぎのコードは、クリックイベントから呼び出す最小例です。

user.js
'use server';

export async function saveUserName(name) {
  console.log(name);
  
  // 保存処理
}
'use client';

import { saveUserName } from "./user";

export default function Page() {
  async function handleClick() {
    await saveUserName("Hanako");
  }

  return (
    <button onClick={handleClick}>
      保存
    </button>
  );
}

呼び出し側から見ると通常の非同期関数のように見えますが、実際にはサーバ側で処理が実行されます。

ただし、クライアントコードからサーバ関数をインポートする場合は、ディレクティブをモジュールレベルで使用する必要があります。
関数レベルの 'use server' はクライアントコンポーネントから直接インポートできません。

サーバ関数の引数と戻り値の制約

サーバ関数はネットワーク越しに呼び出されるため、通信可能な形式へ変換できる値だけが利用できます。
つまり、サーバ関数の引数や戻り値には、React がサポートするシリアライズ可能な値を利用する必要があります。

シリアライズ可能な値については、前回の説明 にくわしく書かれています。

サーバ関数の引数や戻り値にはシリアライズ可能な値のみ利用できる

  • :white_check_mark: OK:プリミティブ、シリアライズ可能な値を含んだ Iterable、Date、FormData、プレーンオブジェクト、サーバ関数(参照)、プロミス
  • :x: NG:JSX要素、通常の関数、クラスインスタンス、イベントハンドラが受け取るイベント

:warning: セキュリティ上の注意

サーバ関数はサーバ上で実行されますが、呼び出し元はクライアントです。

そのため、受け取る引数は常に改ざん可能な入力として扱い、入力検証や認可チェックを必ずサーバ側で行う必要があります。

入力値の検証を行う

'use server';

export async function updateProfile(name) {
  if (typeof name !== "string") {
    throw new Error("Invalid input");
  }

  if (name.length > 50) {
    throw new Error("Too long");
  }

  // 更新処理
}

権限チェックを行う

'use server';

export async function deleteUser(id) {
  const currentUser = await getCurrentUser();

  if (!currentUser.isAdmin) {
    throw new Error("Forbidden");
  }

  // 削除処理
}

taint API(実験的)

React には、サーバ関数から機密データが誤ってクライアントへ渡ることを防ぐための taint API という実験的な機能があります。

この taint API を使い、機密データの値やオブジェクトをクライアントにそのまま渡してはならないものとして React に登録することで、意図しないデータ漏洩を検出できます。

ただし現時点では実験段階の機能であるため、Reactの安定版ではまだ利用できません。
まずは入力値の検証や権限チェックを適切に行うことが重要です。

サーバ関数への入力は常に信頼できないものとして扱うこと

  • 入力値の検証を必ずサーバ側で行う
  • 権限チェックをサーバ関数の中で行う
  • taint API は実験的機能のため、本番環境では使用しないこと

おわりに

今回は 'use server' ディレクティブとサーバ関数の基本を学びました。
次回のフォーム連携編は @player_Moke さん、よろしくおねがいします...!

参照

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?