React - サーバーアクション

WBOY
WBOYオリジナル
2024-08-09 07:34:121022ブラウズ

アクションフォームに反応します。

React は、ネイティブ フォームを強化し、クライアントとサーバーの通信を合理化するために、新しいフォーム アクションと関連フックを導入しました。これらの機能により、開発者はフォーム送信をより効率的に処理できるようになり、ユーザー エクスペリエンスとコードの保守性の両方が向上します。 React フォーム アクションの詳細については、React フォーム アクションに関する私の投稿の詳細な投稿を参照してください。

サーバーアクション

React 18 では、サーバー コンポーネント機能が導入されました。サーバー コンポーネントはサーバーサイド レンダリング (SSR) ではなく、実行時とビルド時の両方でサーバー上で排他的に実行されます。これらのコンポーネントは、データベースやファイル システムなどのサーバー側のリソースにアクセスできますが、イベント リスナーやフックなどのクライアント側のアクションを実行することはできません。

前提条件

サーバー コンポーネントとサーバー アクションの機能を実証するために、Next.js と Prisma を使用します。

Next.js は、フルスタック Web アプリケーションを構築するための React フレームワークです。 React Components を使用してユーザー インターフェイスを構築し、Next.js を追加機能と最適化に使用します。 Next.js は内部的に、バンドルやコンパイルなど、React に必要なツールを抽象化し、自動的に構成します。これにより、構成に時間を費やすことなく、アプリケーションの構築に集中できます。さらに詳しく

Prisma はデータベースのアクセスと操作を簡素化し、SQL を書かずにデータのクエリと操作を可能にする ORM です。さらに詳しく

初期セットアップ
新しい Next.js アプリケーションを作成することから始めます:
糸は次のアプリサーバーの例を作成します

初期のフォルダー構造は次のようになります:

React - Server Actions

Canary リリースにアップグレードすると、サーバー アクションを含む React 19 の機能にアクセスできます:

yarn add next@rc react@rc react-dom@rc

Prisma をインストール

yarn add prisma

Prisma 構成
src/lib/prisma/schema.prisma:
に Prisma スキーマ ファイルを作成します。

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  age Int
}

デモンストレーションの目的で、SQLite を使用しています。運用環境では、より堅牢なデータベースを使用する必要があります。

次に、Prisma クライアント ファイルを src/lib/prisma/prisma.ts に追加します

// ts-ignore 7017 is used to ignore the error that the global object is not
// defined in the global scope. This is because the global object is only
// defined in the global scope in Node.js and not in the browser.

import { PrismaClient } from '@prisma/client'

// PrismaClient is attached to the `global` object in development to prevent
// exhausting your database connection limit.
//
// Learn more:
// https://pris.ly/d/help/next-js-best-practices

const globalForPrisma = global as unknown as { prisma: PrismaClient }

export const prisma = globalForPrisma.prisma || new PrismaClient()

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma

export default prisma

package.json で Prisma を構成します:

{
  //other settings
  "prisma": {
    "schema": "src/lib/prisma/schema.prisma",
    "seed": "ts-node src/lib/prisma/seed.ts"
  }
}

そして、tsconfig.json の TypeScript 設定を更新します。

{
  //Other settings here...

  "ts-node": {
    // these options are overrides used only by ts-node
    // same as the --compilerOptions flag and the 
    // TS_NODE_COMPILER_OPTIONS environment variable
    "compilerOptions": {
      "module": "commonjs"
    }
  }
}

ts-node をグローバルにインストールします:

yarn global add ts-node

初期データをシード中
src/lib/prisma/seed.ts にシード ファイルを追加して、初期データを設定します:

import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
  await prisma.user.create({
    email: "anto@prisma.io",
    name: "Anto",
    age: 35,
  });
  await prisma.user.create({
    email: "vinish@prisma.io",
    name: "Vinish",
    age: 32,
  });
}
main()
  .then(async () => {
    await prisma.$disconnect();
  })
  .catch(async (e) => {
    console.error(e);
    await prisma.$disconnect();
    process.exit(1);
  });

Prisma クライアントをインストールします

yarn add @prisma/client

移行コマンドを実行します:

yarn prisma migrate dev --name init

シードデータが反映されない場合は、手動で追加してください:

yarn prisma db seed

すごいですね!インストールの準備ができたので、データベース操作を実行するアクション ファイルを作成できます。

サーバーアクションの作成
サーバー アクションは、クライアントとサーバーのシームレスな相互通信を可能にする強力な機能です。 src/actions/user.ts にデータベース操作用のファイルを作成しましょう:

"use server";
import prisma from '@/lib/prisma/prisma'
import { revalidatePath } from "next/cache";

// export type for user
export type User = {
  id: number;
  name: string | null;
  email: string;
  age: number;
};


export async function createUser(user: any) {
  const resp = await prisma.user.create({ data: user });
  console.log("server Response");
  revalidatePath("/");
  return resp;
}

export async function getUsers() {
  return await prisma.user.findMany();
}

export async function deleteUser(id: number) {
  await prisma.user.delete({
    where: {
      id: id,
    },
  });
  revalidatePath("/");
}

サーバーコンポーネントの実装

データベースからデータを読み取ってレンダリングするための React サーバー コンポーネントを作成しましょう。 src/app/serverexample/page.tsx:
を作成します。

import UserList from "./Users";
import "./App.css"

export default async function ServerPage() {
  return (
    <div classname="app">
      <header classname="App-header">
        <userlist></userlist>
      </header>
    </div>
  );
}

src/app/serverexample/App.css にスタイルを追加します

.App {
  text-align: center;
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

input {
  color: #000;
}

.App-link {
  color: #61dafb;
}

ユーザーリストを取得してレンダリングするコンポーネントを作成します:
src/app/serverexample/UserList.tsx

import { getUsers } from "@/actions/user";
import { UserDetail } from "./UserDetail";

export default async function UserList() {
  //Api call to fetch User details
  const users = await getUsers();

  return (
    <div classname="grid grid-cols-3 gap-5">
      {users.length ? (
        users.map((user) => <userdetail user="{user}"></userdetail>)
      ) : (
        <div classname="col-span3 opacity-60 text-sm text-center">
          No User found
        </div>
      )}
    </div>
  );
}

src/app/serverexample/UserDetail.tsx

export function UserDetail({ user }) {
  return (
    <div classname="flex items-center gap-4 border border-gray-600 py-1 px-4">
      <img classname="w-10 h-10 rounded-full" src="https://api.dicebear.com/9.x/personas/svg?seed=Shadow" alt="React - サーバーアクション">
      <div classname="font-medium text-base dark:text-white">
        <div>{user.name}</div>
        <div classname="text-sm text-gray-500 dark:text-gray-400">
          {user.email}
        </div>
      </div>
    </div>
  );
}

開発サーバーを実行します:

yarn dev

http://localhost:3000/serverexample に移動して、表示されたユーザー リストを確認します。
React - Server Actions

デフォルトでは、「use client」ディレクティブを指定しない限り、Next.js のコンポーネントはサーバー コンポーネントです。 2 つの重要な点に注意してください:

  1. 非同期コンポーネントの定義: サーバー コンポーネントは再レンダリングされず、一度だけ生成されるため、非同期にすることができます。
  2. データの取得: const users = await getUsers(); の行サーバーからデータをフェッチし、実行時にレンダリングします。

サーバーアクションの調査

サーバー アクションにより、クライアントとサーバーのシームレスな相互通信が可能になります。新しいユーザーを作成するフォームを追加しましょう。

src/app/serverexample/AddUser.tsx に新しいファイルを作成します:

"use client";

import "./app.css";
import { useActionState } from "react";
import { createUser } from "../../actions/user";

const initialState = {
  error: undefined,
};

export default function AddUser() {
  const submitHandler = async (_previousState: object, formData: FormData) => {
    try {
      // This is the Server Action method that transfers the control 
      // Back to the server to do DB operations and get back the result.
      const response = await createUser({
        name: formData.get("name") as string,
        email: formData.get("email") as string,
        age: parseInt(formData.get("age") as string),
      });
      return { response };
    } catch (error) {
      return { error };
    }
  };
  const [state, submitAction, isPending] = useActionState(
    submitHandler,
    initialState
  );

  return (
    <div classname="mt-10">
      <h4 classname="text-center">Add new User</h4>{" "}
      <form action="%7BsubmitAction%7D" classname="text-base">
        <div classname="mt-6 text-right">
          Name:{" "}
          <input classname="ml-2" required name="name" type="text" placeholder="Name">
        </div>
        <div classname="mt-6 text-right">
          Email:{" "}
          <input classname="ml-2" name="email" type="email" placeholder="Email">
        </div>
        <div classname="mt-6 text-right">
          Age:{" "}
          <input classname="ml-2" name="age" type="text" placeholder="Age">
        </div>
        <div classname="mt-6 text-right">
          <button disabled classname="bg-green-600 text-white px-5 py-1 text-base disabled:opacity-30">
            {isPending ? "Adding" : "Add User"}
          </button>
        </div>

        {(state?.error as string) && <p>{state.error as string}</p>}
      </form>
    </div>
  );
}

src/app/serverexample/page.tsx を更新して、AddUser コンポーネントを含めます:

import UserList from "./UserList";
// Import new line
import AddUser from "./AddUser";
import "./App.css"

export default async function ServerPage() {
  return (
    <div classname="app">
      <header classname="App-header">
        <userlist></userlist>
        {/* insert Add User here */}
        <adduser></adduser>
      </header>
    </div>
  );
}

アプリケーションを実行すると、サーバー側の処理がシームレスに処理され、フォーム経由で新しいユーザーを追加できるようになります。
React - Server Actions

The AddUser Component and Seamless Client-Server Interaction

The AddUser component is at the heart of this example, showcasing how React Server Actions can revolutionize the way we handle client-server interactions. This component renders a form for adding new users and leverages the useActionState hook to create a smooth and seamless bridge between the client-side interface and server-side operations.

How It Works

  1. Form Rendering and Data Handling:
  • The AddUser component provides a form where users can input their name, email, and age.
  • Upon form submission, the data is captured and prepared to be sent to the server.
  1. useActionState Hook:
  • The useActionState hook is a crucial part of this setup. It simplifies the complexity of managing client-side state and server-side actions by abstracting them into a unified interface.
  • This hook accepts an asynchronous handler function, which processes the form data and then calls a Server Action method.
  • The brilliance of this approach lies in its abstraction: it feels as though you’re invoking a regular function within the same file, even though it actually triggers a server-side operation.
  1. Server Action Method:
  • The createUser function, defined as a Server Action, executes on the server side. It takes the user data from the form, performs the necessary database operations via Prisma, and returns the result.
  • This server-side method is crucial for maintaining a clean separation between the client and server, while still enabling them to communicate effectively.
  1. Seamless Integration:

From the perspective of a developer working on the client side, it appears as if the form submission is handled locally. However, the heavy lifting such as database manipulation occurs on the server.
The useActionState hook encapsulates this process, managing the state transitions and handling errors, while maintaining an intuitive API for developers.

Server Actions Without Forms

So that's with forms, now lets test an example without forms.
update src/app/serverexample/UserDetail.tsx

"use client";
import { deleteUser } from "@/actions/user";
import { useTransition } from "react";

export function UserDetail({ user }) {
  const [pending, startTransition] = useTransition();

  const handleDelete = () => {
    startTransition(() => {
      deleteUser(user.id);
    });
  };

  return (
    <div classname="flex items-center gap-4 border border-gray-600 py-1 px-4">
      {pending ? (
        <p>Deleting...</p>
      ) : (
        
          <img classname="w-10 h-10 rounded-full" src="https://api.dicebear.com/9.x/personas/svg?seed=Shadow" alt="React - サーバーアクション">
          <div classname="font-medium text-base dark:text-white">
            <div>{user.name}</div>
            <div classname="text-sm text-gray-500 dark:text-gray-400">
              {user.email}
            </div>
          </div>
          <button classname="ml-auto" onclick="{handleDelete}">
            <img classname="w-4 h-4" src="/delete.png" alt="">
          </button>
        >
      )}
    </div>
  );
}

Key Points:

  • Server Action: deleteUser(user.id) is a server action that removes the user from the database. This operation is triggered without any form submission.
  • useTransition: This hook allows you to manage the asynchronous state of the deletion process, showing a "Deleting..." message while the operation is in progress.
  • User Interface: The component maintains a clean UI, dynamically updating based on the action status.

Now, you can seamlessly delete a user within the application:
React - Server Actions

Conclusion

This approach is transformative because it abstracts away the complexities of client-server communication. Traditionally, such interactions would require handling API endpoints, managing asynchronous requests, and carefully coordinating client-side state with server responses. With React Server Actions and the useActionState hook, this complexity is reduced, allowing developers to focus more on building features rather than worrying about the underlying infrastructure.

By using this pattern, you gain:

  • Cleaner Code: The client-side code remains simple and focused, without the need for explicit API calls.
  • Improved Developer Experience: Server-side operations are seamlessly integrated, reducing cognitive load and potential for errors.
  • Enhanced Performance: Server Actions are optimized for performance, reducing unnecessary client-server round trips and ensuring that server-side resources are used efficiently.

You can find the full code in the repository

以上がReact - サーバーアクションの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。