Next.JS 中服务器操作的详细指南

Next.js 提供了多种在服务器上获取和更改数据的技术,技术的选择取决于具体的用例和项目需求。"服务器操作"是一种在服务器上获取和更改数据的技术,在 Next.js 13.4 中作为 alpha 功能引入,并在 Next.js 14 中变得稳定,并提供了一种强大的机制,用于在服务器上执行代码以响应正如本文将解释的那样,与传统方法相比,客户交互具有多种优势。

服务器操作是在服务器上运行以响应客户端交互的异步 JavaScript 函数。 它们允许开发人员执行服务器端代码,而无需显式定义 API 路由。可以通过单击按钮或提交表单来调用它们。当用户执行操作时,例如提交表单或单击按钮,服务器会收到通知,并触发特定函数来处理请求。它们通常用于执行需要访问服务器端资源(例如数据库或文件系统)的任务。

使用服务器操作的好处

在 Next JS 中使用服务器操作有几个好处,包括:

  • 减少客户端 JavaScript: 由于服务器操作仅在服务器上定义和运行,因此它们不包含在客户端捆绑包中,从而最大限度地减少了浏览器中所需的 JavaScript 代码量,从而减少了初始页面加载时间并提高了整体性能。
  • 服务器端数据突变: 服务器操作可以在服务器上直接进行数据突变,从而无需创建和管理单独的 API 端点。服务器操作通过避免不必要的网络往返来简化数据管理并提高性能。
  • 增强的可访问性: 由于服务器操作 JS 代码未发送到客户端,因此即使禁用 JavaScript,表单和交互元素也能发挥作用,以确保为有可访问性需求的用户提供更广泛的覆盖范围。
  • 改进的性能: 服务器操作通过减少客户端 JavaScript 并启用服务器端数据突变来提高页面加载时间和整体应用程序响应能力。
  • 更大的灵活性: 服务器操作可以处理广泛的任务,从简单的数据检索到复杂的业务逻辑。这种多功能性使它们成为 Web 开发的强大工具。
  • 支持渐进增强的表单: 服务器操作可用于开发渐进增强的表单。这意味着即使 JavaScript 被禁用,表单也将保持功能,而在启用 JavaScript 时,表单将提供增强的用户体验。
  • 重新验证缓存数据的能力: 服务器操作可用于重新验证缓存数据,确保数据保持最新。
  • 重定向用户的能力: 服务器操作可以在执行特定操作后重定向用户。例如,他们可以在成功登录后将用户重定向到其主页。

如何创建服务器操作

服务器操作可以在两个地方创建。

  • 在使用它的服务器组件内部。
javascript 复制代码
export default function Product() {
  async function addItemToCart() {
    "use server";
    // Server Action for adding an item to the server-side cart goes here
  }
  return <div>Products Page</div>
}

请注意该use server指令。它指示 Next JS 将该异步函数视为服务器操作。

  • 在单独的文件中,例如,actions.js指令use server位于顶部。
javascript 复制代码
"use server";

export async function addItemToCart() {
  // Server Action for adding an item to the server-side cart goes here
}

export async function removeItemFromCart() {
  // Server Action for removing an item from the server-side cart goes here
}

在这种方法中,我们可以在单个文件中创建多个可重用的服务器操作,使它们可用于client组件server

如何使用服务器操作

我们在上一节中创建的服务器操作可以以多种方式使用。

  • 使用在form标签的属性action
javascript 复制代码
export default function Product() {
  async function addItemToList() {
    "use server";
    // Server Action goes here
  }

  return (
    <form action={addItemToList}>
      <input type="text" name="item" />
      <button type="submit">Add Item to TODO List</button>
    </form>
  );
}
  • form内的子级button input上使用该属性formAction
javascript 复制代码
export default function Product() {
  async function addItemToList() {
    "use server";
    // Server Action goes here
  }

  return (
    <form>
      <input type="text" name="item" />
      <button type="submit" formAction={addItemToList}>Add Item to TODO List</button>
    </form>
  );
}
  • 使用startTransition:通过将服务器操作包装在客户端组件中的钩子startTransition函数中。useTransition例如,在名为 的客户端组件中Form.tsx
javascript 复制代码
"use client";
import { addItemToList } from "@/lib/actions";
import { useState, useTransition } from "react";

export default function Form() {
  const [item, setItem] = useState("");
  const [isPending, startTransition] = useTransition();

  return (
    <div className="flex flex-col gap-2">
      <input
        type="text"
        value={item}
        onChange={(e) => setItem(e.target.value)}
      />
      <button
        onClick={() => startTransition(() => addItemToList(item))}>
        Add Item to TODO List
      </button>
    </div>
  );
}

我们将服务器操作作为回调传递给startTransition函数,单击按钮时将调用该操作。

它与 API 路线有何不同?

服务器操作和传统 API 路由之间存在一些差异。让我们通过考虑以下场景来了解这一点。

场景1

让我们考虑具有购物车功能的电子商务应用程序的经典示例。在 API 路由方法中,用户使用必要的负载向 API 路由发出 HTTP 请求。随后,执行API路由,将商品添加到购物车,并将新添加的商品返回给客户端。在客户端组件中,我们添加新添加的商品并向用户显示更新后的购物车。

但是,在服务器操作方法中,用户可以触发服务器操作以将商品从服务器组件添加到购物车。执行服务器操作,并将商品添加到服务器上的购物车中。服务器操作可以重新验证路径并在服务器组件中显示更新的购物车,而不是将响应返回给客户端并让客户端显示更新的购物车项目。

场景2

让我们考虑一个博客应用程序的示例,该应用程序具有用于创建、阅读、更新和删除博客文章的常用 CRUD 操作,以及其他所需的功能。如果我们在这种情况下使用 API 路由,我们可以轻松地将 API 路由公开给第三方应用程序,这些应用程序可以使用 API 与我们的博客构建集成。但是,由于服务器操作与应用程序紧密耦合,因此我们无法将它们公开给任何第三方应用程序。在这种情况下,服务器操作可能是一个缺点。

现在出现了一个明显的问题:我应该何时何地使用服务器操作?嗯,正如您从上面看到的,它总是取决于特定的用例。除了我们在前面几节中看到的好处之外,您还可以:

  • 如果您的应用程序处理大量表单,请使用服务器操作,因为操作函数在提交时自动接收 FormData,并减少维护大量反应状态来存储表单输入的痛苦。此外,它们与 React 的实验useFormStatususeFormState钩子集成得很好。
  • 如果您的应用程序需要caching and revalidation功能,请使用服务器操作,因为服务器操作除了改变数据之外,还可以用于缓存失效和路径重新验证。

使用服务器操作的示例

让我们通过在通讯录应用程序中创建和使用服务器操作来了解服务器操作的工作原理。

通过运行以下命令下载起始代码,

bash 复制代码
git clone --branch starter git@github.com:nirmal1064/nextjs-server-actions-tutorial.git

## Run the below command to install dependencies and generate Prisma client
npm install
npx prisma db push

起始代码概述

  • 我们Tailwind CSS使用Prisma ORM本地SQLite DB. (您可以使用您选择的任何数据库)。
  • 我们在目录中创建了两个服务器组件componentsContactForm并且ContactCard.
  • ContactForm- 该组件包含一个简单的表单,其中包含联系人姓名、电话和城市的三个输入字段以及用于表单提交的按钮。
  • ContactCard- 该组件显示联系人的详细信息,并在底部有两个按钮用于viewingdeleting联系人。
  • ContactForm用于ContactCard根路由器的 ( /) page.tsx,其中ContactForm位于顶部用于添加新联系人。相反,现有联系人列表是使用该ContactCard组件呈现在下面的。
  • 同样,在[id]/view路线中,我们使用该组件显示单个联系人详细信息ContactCard
  • 在该lib目录中,我们有db.ts配置数据库连接的文件。
  • 在该schema.prisma文件中,我们创建了一个Contact模型,其中包含联系人的namephone和 等详细信息。city

添加新联系人的服务器操作

让我们探索使用服务器操作的不同方法并演示其功能。

首先,我们将创建一个服务器操作以将新联系人添加到数据库中。此服务器操作将在同一文件中定义。

打开ContactForm.tsx文件并添加服务器操作以添加新联系人。

ini 复制代码
import prisma from "@/lib/db";

export default function ContactForm() {
  async function addContact(formData: FormData) {
    "use server";
    await prisma.contact.create({
      data: {
        name: formData.get("name") as string,
        phone: formData.get("phone") as string,
        city: formData.get("city") as string,
      },
    });
  }

  return (
    <form className="flex flex-col gap-2" action={addContact}>
      <input
        type="text"
        name="name"
        placeholder="Contact Name"
        className="p-2 border border-black"
      />
      <input
        type="text"
        name="phone"
        placeholder="Contact Phone Number"
        className="p-2 border border-black"
      />
      <input
        type="text"
        name="city"
        placeholder="Enter City"
        className="p-2 border border-black"
      />
      <button type="submit" className="bg-blue-500 text-white rounded-md p-1">
        Add Contact
      </button>
    </form>
  );
}

ContactForm组件中,我们创建了addContact服务器操作,在form元素中,我们将 传递addContact给了action属性。由于我们从表单元素调用服务器操作,因此服务器操作函数隐式获取类型为 的参数FormDatause server我们在函数体的顶部添加了指令。然后,我们使用 Prisma 客户端的创建 API 在数据库中创建一个新联系人。它从参数中提取数据formdata并创建一个具有指定姓名、电话号码和城市的新联系人对象。

就是这样。服务器操作已准备就绪。当用户提交表单时,服务器操作将创建新联系人并将其保存到数据库中。但是,新添加的联系人不会立即在 UI 中可见。为了解决这个问题,我们必须添加模块revalidatePath中的方法next/cache

csharp 复制代码
import { revalidatePath } from "next/cache";

// In the addContact Function
async function addContact(formData: FormData) {
  "use server";
  await prisma.contact.create({
    data: {
      name: formData.get("name") as string,
      phone: formData.get("phone") as string,
      city: formData.get("city") as string,
    },
  });
  // Add the below line
  revalidatePath("/");
}

这将确保/在将联系人保存到数据库中后重新验证路径。这可确保 UI 反映最新的联系人数据。

打开终端,运行npm run dev命令,然后转到localhost:3000. 在表单中,输入联系方式,然后单击"添加联系人"。您应该能够创建新联系人,该联系人会在 UI 中立即更新。请检查下面的演示。

上面的演示说明了提交表单时会创建一个新联系人,并确保revalidateTag重新验证路径以在屏幕上显示新添加的联系人。

此部分的服务器操作代码可以在此 GitHub提交中找到。

注意: 如果我们要使用 API 路由方法实现相同的功能,我们通常会创建一个表单组件以及 React 状态来处理表单输入。随后,我们必须在目录中定义 API 路由**api**。之后,我们必须向 API 端点发出 POST 请求,以将联系人保存在数据库中并返回新创建的联系人。然而,使用服务器操作,可以以更简单的方式完成此任务。

更新现有联系人的服务器操作

在该ContactCard组件中,我们有一个view按钮,它将用户引导到[id]/view显示特定联系人详细信息的页面。我们将重用ContactForm此页面上的组件来提供更新现有联系人的更新功能。我们将联系人作为可选属性传递给该组件,以使该组件可ContactForm重用于此用例。我们将其设为可选,因为在新联系人的情况下我们不需要此道具。

修改ContactForm组件以添加可选道具。

ini 复制代码
import prisma from "@/lib/db";
import { revalidatePath } from "next/cache";

type Props = { contact?: Contact | null };

export default function ContactForm({ contact }: Props) {
  async function addContact(formData: FormData) {
    "use server";
    await prisma.contact.create({
      data: {
        name: formData.get("name") as string,
        phone: formData.get("phone") as string,
        city: formData.get("city") as string,
      },
    });
    revalidatePath("/");
  }

  return (
    <form
      className="flex flex-col gap-2"
      action={contact ? updateContact : addContact}
    >
      <input
        type="text"
        name="name"
        placeholder="Contact Name"
        defaultValue={contact?.name} // Passing the default value in case of update contact
      />
      <input
        type="text"
        name="phone"
        placeholder="Contact Phone Number"
        defaultValue={contact?.phone} // Passing the default value in case of update contact
      />
      <input
        type="text"
        name="city"
        placeholder="Enter City"
        defaultValue={contact?.city} // Passing the default value in case of update contact
      />
      <button type="submit" className="bg-blue-500 text-white rounded-md p-1">
        {/* Changing the button title based on add/update contact */}
        {contact ? "Update Contact" : "Add Contact"}{" "}
      </button>
    </form>
  );
}

在这里,我们将联系人作为可选属性传递。对于更新场景,元素defaultValue的属性input用于用现有的联系信息预填充字段。按钮标题是根据添加还是更新操作动态生成的。类似地,在元素action的属性中,我们根据添加/更新功能form有条件地调用addContactor 。updateContact服务器updateContact操作将在以下步骤中创建。

让我们创建一个服务器操作来更新现有联系人。在ContactForm组件中添加updateContact服务器操作。

php 复制代码
async function updateContact(formData: FormData) {
  "use server";
  await prisma.contact.update({
    where: { id: contact?.id },
    data: {
      name: formData.get("name") as string,
      phone: formData.get("phone") as string,
      city: formData.get("city") as string
    }
  });
  revalidatePath(`/${contact?.id}/view`);
}

与"addContact Server Action, theupdateContact Server Action also takes theformData as the input argument and extracts the data from the form fields. Then, for updating the contact, we use Prisma Client's update API by passing the contact's ID in the where clause and the form data in the data object. In therevalidatePath"类似,我们正在重新验证当前的联系人路径以使 UI 立即更新。

现在将其添加ContactForm到目录page.tsx内部app/[id]/view,如下所示。

typescript 复制代码
import ContactForm from "@/components/ContactForm";
import prisma from "@/lib/db";

type Props = { params: { id: string } };

async function getContactById(id: string) {
  return await prisma.contact.findUnique({ where: { id } });
}

export default async function EditContact({ params }: Props) {
  const contact = await getContactById(params.id);

  return (
    <main className="flex flex-col min-h-screen justify-center items-center bg-gray-50">
      {/* Adding the ContactForm above the Details */}
      <ContactForm contact={contact} />
      <h1 className="font-semibold">Single Contact</h1>
      {contact ? (
        <ContactCard key={contact.id} contact={contact} />
      ) : (
        <p>Contact Does Not Exist</p>
      )}
    </main>
  );
}

单击view联系人卡片上的按钮打开单个联系人。尝试编辑其中一个字段并单击按钮Update Contact。这将更新数据库中的联系人并在 UI 中显示更新后的联系人,如下面的演示所示。

上面的演示说明,当使用不同的值提交表单时,联系人会更新,并确保revalidateTag重新验证当前路径以立即显示更新的联系人。

此部分的服务器操作代码可以在此 GitHub提交中找到。

删除联系人的服务器操作

让我们创建一个服务器操作来实现删除联系人功能。可以从显示所有联系人的主页和单个联系人视图页面触发删除功能。但是,服务器组件无法利用按钮的onClick事件处理程序。我们将把Delete按钮提取到一个单独的客户端组件中来解决这个问题。此外,服务器操作必须在单独的文件中创建,因为我们无法在客户端组件内定义服务器操作。让我们从创建服务器操作开始。

在目录中创建一个actions.ts文件lib并添加服务器操作以删除联系人。

javascript 复制代码
"use server";

import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import prisma from "./db";

export async function deleteContactById(id: string, inViewRoute = false) {
  await prisma.contact.delete({ where: { id } });
  if (inViewRoute) {
    redirect("/");
  } else {
    revalidatePath("/");
  }
}

我们创建了服务器操作函数deleteContactById,用于从数据库中删除联系人。它需要两个参数:

  • Id:要删除的联系人的唯一标识符。此参数传递给wherePrisma 客户端deleteAPI 中的子句,以从数据库中删除指定的联系人。
  • inViewRoute:一个标志,指示删除请求是来自主页还是查看页面。根据此标志的值,该函数将用户重定向到主页,或者如果请求来自主页,则触发数据重新验证。

现在,让我们DeleteButton在目录中创建组件并从按钮components调用服务器操作。onClick

typescript 复制代码
"use client";
import { deleteContactById } from "@/lib/actions";
import { usePathname } from "next/navigation";

type Props = { id: string };

export default function DeleteButton({ id }: Props) {
  const pathName = usePathname();

  return (
    <button
      className="bg-red-600 px-1 py-0.5 rounded-md text-white"
      onClick={() => deleteContactById(id, pathName === "/" ? false : true)}>
      Delete
    </button>
  );
}

我们创建了DeleteButton以 contactid作为 prop 的组件,并使用usePathnamehook 来获取当前路线。服务器操作是通过按钮的onClick事件处理程序触发的。正如前面所讨论的,我们还可以使用钩子startTransition的功能useTransition来调用Server Action以进行非阻塞的UI更新。

现在,将DeleteButton组件添加到组件中ContactCard

javascript 复制代码
import DeleteButton from "./DeleteButton";
import Link from "next/link";

type Props = { contact: Contact };

export default function ContactCard({ contact }: Props) {
  return (
    <div className="w-[250px] bg-white shadow-md p-2">
      <p>
        Name: <span className="font-semibold">{contact.name}</span>
      </p>
      <p>
        Phone: <span className="font-semibold">{contact.phone}</span>
      </p>
      <p>
        City: <span className="font-semibold">{contact.city}</span>
      </p>
      <div className="flex gap-2">
        <Link href={`/${contact.id}/view`}>
          <button className="bg-sky-600 px-1 py-0.5 rounded-md text-white">
            View
          </button>
        </Link>
        {/* Add the Delete Button here */}
        <DeleteButton id={contact.id} />
      </div>
    </div>
  );
}

在浏览器中打开应用程序并从主页测试删除功能。

  • 从主页演示中删除联系人

上面的演示说明,一旦单击删除按钮,联系人就会被删除,并确保revalidateTag当前路径重新生效。UI 已更新以显示可用的联系人。

  • 从查看页面删除联系人演示

上面的演示说明,一旦单击删除按钮,联系人就会被删除,并且该redirect功能会将用户重定向到主页,并且可用的联系人会显示在 UI 中。

就是这样。此部分的服务器操作代码可以在此 GitHub提交中找到

概括

  • 服务器操作必须是带有use server指令的异步函数。
  • 它们在服务器组件或单独的文件中定义,指令use server位于顶部。
  • 它们可以直接从元素action的属性formformAction表单内按钮的属性调用。
  • 可以在单个表单内调用完成不同任务的多个服务器操作。
  • 可以从客户端组件调用它们,前提是所需的服务器操作是在单独的文件中定义的。
  • 服务器操作可用于revalidate路径和标签,以及用于redirect不同路线的用户。

结论

在本文中,我们讨论了服务器操作的概念、它们相对于传统 API 路由的优势,以及在 Next.JS 应用程序中创建和使用服务器操作的不同方法。通过减少客户端 JavaScript、增强可访问性并启用服务器端数据突变,服务器操作简化了开发流程并改善了用户体验。如果您希望使用 Next.js 构建现代 Web 应用程序,服务器操作是一个必不可少的工具。尝试使用服务器操作来增强您的应用程序。

谢谢阅读。如果您觉得这篇文章有用,请在评论部分告诉我。如果您有任何疑问,请随时发表评论。

本文完整的源代码可以在我的GitHub上找到。如果您发现该代码有用,请给它一颗星。 关注我Shopify网站优化

相关推荐
新缸中之脑5 分钟前
Llama 3.2 安卓手机安装教程
前端·人工智能·算法
hmz8569 分钟前
最新网课搜题答案查询小程序源码/题库多接口微信小程序源码+自带流量主
前端·微信小程序·小程序
看到请催我学习15 分钟前
内存缓存和硬盘缓存
开发语言·前端·javascript·vue.js·缓存·ecmascript
blaizeer44 分钟前
深入理解 CSS 浮动(Float):详尽指南
前端·css
速盾cdn1 小时前
速盾:网页游戏部署高防服务器有什么优势?
服务器·前端·web安全
小白求学11 小时前
CSS浮动
前端·css·css3
什么鬼昵称1 小时前
Pikachu-csrf-CSRF(POST)
前端·csrf
XiaoYu20022 小时前
22.JS高级-ES6之Symbol类型与Set、Map数据结构
前端·javascript·代码规范
golitter.2 小时前
Vue组件库Element-ui
前端·vue.js·ui
golitter.2 小时前
Ajax和axios简单用法
前端·ajax·okhttp