网站首页

人工智能P2P分享Wind搜索发布信息网站地图标签大全

当前位置:诺佳网 > 软件工程 > 前端开发 > Vue >

总结 Next.js 中的 Server Actions

时间:2025-12-27 21:46

人气:

作者:admin

标签:

导读:#129489;‍#128187; 写在开头 点赞 收藏 学会#129315;#129315;#129315; 我们知道,Next.js 最核心的特性便是 支持静态生成(SSG)和服务端渲染(SSG),这也就意味着我们可以以部署 Node 服务的方式...

点赞 + 收藏 === 学会????????????

我们知道,Next.js 最核心的特性便是 支持静态生成(SSG)和服务端渲染(SSG),这也就意味着我们可以以部署 Node 服务的方式,将其部署在服务器上,用请求后端接口类似的形式来请求页面文件。换句话说,我们其实可以直接把 Next.js 看成一个特殊的 Node 后端服务。

既然是在服务端进行运行,那么它在数据库的查询方式上自然和一般的 SPA 客户端进行查询有所区别。

我们先简单分析一下一般 React SPA 项目的前后端交互:

  1. 前后端分离开发,后端无论用什么语言编写,最终只需要提供一个 API Endpoint(URL) 给前端。
  2. 前端二次封装 axios,或者直接使用 fetch,自己手动封装对应 URL 的异步请求函数,根据 API Endpoint 的不同依次进行封装,并定义需要传入的参数、方法等。
  3. 前端在需要调用接口的位置调用这些封装好的接口函数获取数据,展示页面。

这样是一套完整的前后端分离接口交互的编写。很显然,这样的交互、从数据库中拿数据的行为是以页面为单位的,当用户重新在这个页面上发生相同的交互行为时,对应的 HTTP 请求会 100%完整地复刻一遍。

而在 Next.js 中,基于其自身的特殊性,提供了一个与数据库直接进行交互的特殊方式:Server Actions

Server Actions 是?

Server Actions 直译为 服务端行为,是 Next.js 中的一个特殊概念,用于在服务端直接进行数据库查询等操作,而不是在客户端进行异步请求。

下面贴一下官方的一些解释:

React Server Actions allow you to run asynchronous code directly on the server. They eliminate the need to create API endpoints to mutate your data. Instead, you write asynchronous functions that execute on the server and can be invoked from your Client or Server Components.

React Server Actions 允许您直接在服务器上运行异步代码。您无需创建 API 端点来更改数据。相反,您可以编写在服务器上执行的异步函数,并可从客户端或服务器组件中调用。

Security is a top priority for web applications, as they can be vulnerable to various threats. This is where Server Actions come in. They offer an effective security solution, protecting against different types of attacks, securing your data, and ensuring authorized access. Server Actions achieve this through techniques like POST requests, encrypted closures, strict input checks, error message hashing, and host restrictions, all working together to significantly enhance your app's safety.

安全性是网络应用程序的重中之重,因为它们很容易受到各种威胁。这就是服务器操作的用武之地。它们提供了一种有效的安全解决方案,可抵御各种类型的攻击、保护数据安全并确保授权访问。Server Actions 通过 POST 请求、加密关闭、严格输入检查、错误信息散列和主机限制等技术来实现这一目标,所有这些技术共同作用,大大提高了应用程序的安全性。

因此简单来说,所谓的 Server Actions 实际上就是一个在 Next.js 中的一个 普通异步函数,只不过这个异步函数可以直接操作数据库而已。这个函数与 React Server Components 深度集成,可以看成是组件本身的一部分。

这个异步函数的执行单纯在服务端进行,而 不以接口请求的形式获取数据,因此 无法在网络调试中看到接口的请求,可以理解为这个异步函数是在服务端直接执行的。

也正是因此,不将请求的数据暴露出来,能够最大程度上保证数据的安全性。

Server Actions 还与 Next.js 缓存深度集成。通过其提交表单时,不仅可以使用该动作更改数据,还可以使用 revalidatePathrevalidateTag 等 Next API 刷新相关页面的缓存,以确保在每次数据更新之后重新访问该页面时能够获取最新的数据。

与传统 API 的比较

我们可以试着总结一下 Server Action 和传统 API Endpoints 的优缺点:

Server Actions

优点:

  1. 简化代码结构

    • 将处理逻辑和组件逻辑放在一起,减少代码分散,提高可读性。例如一般处理点击按钮触发请求,我们可以直接往 onClick 中传递 Server Action 异步函数即可,无需调用 API Endpoint。
  2. 直接与组件交互

    • Server Actions 直接在组件中调用,减少了不必要的中间层,简化了数据流。
  3. 减少网络请求

    • 通过 Server Actions 直接在服务器上处理数据,不需要通过 HTTP 请求来获取数据,减少了网络延迟。
  4. 自动处理错误

    • Server Actions 可以自动处理常见的错误,如网络错误或数据库连接错误,使得代码更加健壮。

缺点:

  1. 难以复用

    • 由于 Server Actions 紧密耦合在组件中,复用性较差,不如 API Endpoints 容易在多个组件或项目中共享。
  2. 测试难度大

    • 测试 Server Actions 可能比测试 API Endpoints 更加复杂,因为它们嵌入在组件中,需要模拟更多的上下文。不过 Next 官方提供的 console 工具可以直接在 Chrome 的 Dev tool 里面进行输出。
  3. 可扩展性

    • 当项目变大时,将所有逻辑放在组件中可能会导致代码难以维护和扩展,需要提前组织好代码的逻辑。

传统 API Endpoints

优点:

  1. 松散耦合

    • API Endpoints 将业务逻辑与前端组件分离,提高了代码的模块化和复用性。
  2. 易于测试

    • API Endpoints 独立于组件,便于进行单元测试和集成测试。可以与各类 API 测试工具相集成,适合前后端分离、大型团队的合作开发。
  3. 灵活性

    • 可以使用各种中间件和框架(如 Express, Koa 等)来扩展和处理复杂的逻辑。
  4. 可扩展性

    • 更容易扩展和维护,适合大型应用程序的开发。

缺点:

  1. 额外的网络开销

    • 每次数据请求都需要通过 HTTP 请求,增加了网络延迟,尤其在高频请求的情况下影响性能。需要使用防抖、节流等方式处理请求。
  2. 复杂性

    • 需要额外的配置和设置来处理 API 请求和响应,增加了项目的复杂性。
  3. 代码分散

    • 数据处理逻辑与组件逻辑分开,可能导致代码分散,降低可读性。

适用场景

  • Server Actions

    • 适合小型项目或简单的数据交互场景。
    • 当需要快速开发、减少代码复杂性时,Server Actions 是一个不错的选择。
    • 适用于处理简单逻辑和不频繁的数据操作。
  • API Endpoints

    • 适合中大型项目或复杂的数据交互场景。
    • 需要高复用性、易测试和可扩展性时,选择 API Endpoints 更为合适。
    • 适用于需要处理复杂业务逻辑、多端共享 API 的场景。

Server Action 实例

上面我们简单讨论了 Server Actions 的定义以及其与一般 API Endpoint 的区别,接下来我们看一个实际使用 Server Action 与数据库进行交互的例子。

此处的例子来源于Next 官方教程,需要的同学可以去官网进行查阅。此处需要实现的目标是对数据库 Invoices(发票)数据的 增、改、删 ,也就是直接修改数据库数据。

我们可以看一下具体的代码,并对它进行解析:

"use server"; // Server Actions 只在服务端运行

import { z } from "zod";
import { sql } from "@vercel/postgres";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";

// zod 是一个用于验证数据的库,它可以帮助我们定义数据的结构,并验证数据是否符合这个结构。
const FormSchema = z.object({
  id: z.string(),
  customerId: z.string(),
  amount: z.coerce.number(), // coerce.number() 会将字符串转换为数字
  status: z.enum(["pending", "paid"]),
  date: z.string(),
});

const CreateInvoice = FormSchema.omit({ id: true, date: true });
export async function createInvoice(formData: FormData) {
  // CreateInvoice.parse 方法用于验证 formData 中的数据是否符合 CreateInvoice 的结构
  const { customerId, amount, status } = CreateInvoice.parse({
    customerId: formData.get("customerId"),
    amount: formData.get("amount"),
    status: formData.get("status"),
  });
  const amountInCents = amount * 100;
  const date = new Date().toISOString().split("T")[0]; // 获取当前日期:YYYY-MM-DD

  await sql`
    INSERT INTO invoices (customer_id, amount, status, date)
    VALUES (${customerId}, ${amountInCents}, ${status}, ${date})
  `;

  revalidatePath("/dashboard/invoices"); // revalidatePath 的作用是重新生成指定路由的页面,以便在下次访问时显示最新数据
  redirect("/dashboard/invoices"); // 重定向
}

const UpdateInvoice = FormSchema.omit({ id: true, date: true });
export const updateInvoice = async (id: string, formData: FormData) => {
  const { customerId, amount, status } = UpdateInvoice.parse({
    customerId: formData.get("customerId"),
    amount: formData.get("amount"),
    status: formData.get("status"),
  });

  const amountInCents = amount * 100; // 将金额转换为分

  await sql`
    UPDATE invoices
    SET customer_id = ${customerId}, amount = ${amountInCents}, status = ${status}
    WHERE id = ${id}
  `;

  revalidatePath("/dashboard/invoices"); // 重新生成指定路由的页面
  redirect("/dashboard/invoices"); // 重定向
};

export const deleteInvoice = async (id: string) => {
  await sql`DELETE FROM invoices WHERE id = ${id}`;
  revalidatePath("/dashboard/invoices");
};

这段代码是一个完整的 actions.ts 文件。对相同对象的数据库操作可以用单独的文件进行存放。

首先我们看一下导入。这里使用了 zod 库用于 TS 数据验证,@vercel/postgres 用于数据库操作,next/cachenext/navigation 用于缓存和导航控制。定义了一个 FormSchema 来验证发票表单数据的结构。

const CreateInvoice = FormSchema.omit({ id: true, date: true });
CreateInvoice 是一个从 FormSchema 中去除 id 和 date 字段的验证器,用于创建发票时的数据验证。
export async function createInvoice(formData: FormData) {
  // CreateInvoice.parse 方法用于验证 formData 中的数据是否符合 CreateInvoice 的结构
  const { customerId, amount, status } = CreateInvoice.parse({
    customerId: formData.get("customerId"),
    amount: formData.get("amount"),
    status: formData.get("status"),
  });
  const amountInCents = amount * 100;
  const date = new Date().toISOString().split("T")[0]; // 获取当前日期:YYYY-MM-DD

  await sql`
    INSERT INTO invoices (customer_id, amount, status, date)
    VALUES (${customerId}, ${amountInCents}, ${status}, ${date})
  `;

  revalidatePath("/dashboard/invoices"); // 重新生成指定路由的页面
  redirect("/dashboard/invoices"); // 重定向
}

createInvoice 函数处理发票创建:

  1. formData 获取并验证数据。
  2. 将金额转换为分(cents)。
  3. 获取当前日期。
  4. 使用 SQL 插入语句将数据插入数据库。
  5. 使用 revalidatePath 重新生成 /dashboard/invoices 路由的页面,以便显示最新数据。
  6. 使用 redirect 重定向到 /dashboard/invoices

其余两函数对 Invoice 的操作大同小异,三个函数分别实现了增、改、删的操作。

那么,对应具体该怎么在组件中使用 Server Actions 呢?

我们对应的来看一下提交表单的内容:

import { CustomerField } from "@/app/lib/definitions";
import Link from "next/link";
import {
  CheckIcon,
  ClockIcon,
  CurrencyDollarIcon,
  UserCircleIcon,
} from "@heroicons/react/24/outline";
import { Button } from "@/app/ui/button";
import { createInvoice } from "@/app/lib/actions";

export default function Form({ customers }: { customers: CustomerField[] }) {
  return (
    <form action={createInvoice}>
      {/* ~此处省略表单的具体内容~ * 
温馨提示:以上内容整理于网络,仅供参考,如果对您有帮助,留下您的阅读感言吧!
相关阅读
本类排行
相关标签
本类推荐

CPU | 内存 | 硬盘 | 显卡 | 显示器 | 主板 | 电源 | 键鼠 | 网站地图

Copyright © 2025-2035 诺佳网 版权所有 备案号:赣ICP备2025066733号
本站资料均来源互联网收集整理,作品版权归作者所有,如果侵犯了您的版权,请跟我们联系。

关注微信