百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

揭秘前端无刷新提交数据的黑科技 - React Server Actions深度解析

suiw9 2024-11-06 20:23 29 浏览 0 评论

在上一章中,您使用 URL 搜索参数和 Next.js API实现了搜索和分页。让我们继续处理发票页面,添加创建、更新和删除发票的功能!

在本章中,我们将要讨论几个主题:

  • React Server Actions 是什么以及如何使用它们来改变数据。
  • 如何使用表单和服务器组件。
  • 使用本机formData对象的最佳实践,包括类型验证。
  • 如何使用revalidatePath API 重新验证客户端缓存。
  • 如何创建具有特定 ID 的动态路线段。

什么是服务器操作?

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

安全性是 Web 应用程序的首要任务,因为它们可能容易受到各种威胁。这就是服务器操作的作用所在。它们提供有效的安全解决方案,可防止不同类型的攻击,保护您的数据并确保授权访问。服务器操作通过 POST 请求、加密闭包、严格输入检查、错误消息哈希和主机限制等技术实现这一点,所有这些技术共同作用,可显著增强应用程序的安全性。

将表单与服务器操作结合使用

在 React 中,你可以使用元素action中的属性<form>来调用操作。操作将自动接收原生的FormData对象,包含捕获的数据。
例如:

// Server Component
export default function Page() {
  // Action
  async function create(formData: FormData) {
    'use server';
 
    // Logic to mutate data...
  }
 
  // Invoke the action using the "action" attribute
  return <form action={create}>...</form>;
}

在服务器组件内调用服务器操作的一个优点是逐步增强 - 即使在客户端禁用 JavaScript,表单仍然有效。

Next.js 与服务器操作

Server Actions 也与 Next.js缓存深度集成。当通过服务器操作提交表单时,您不仅可以使用该操作来改变数据,还可以使用revalidatePath和等API重新验证相关缓存revalidateTag,接下来让我们看看它们是如何协同工作的!

创建发票

以下是创建新发票需要采取的步骤:

  • 创建一个表单来捕获用户的输入。
  • 创建一个服务器操作并从表单调用它。
  • 在服务器操作中,从对象中提取数据formData。
  • 验证并准备要插入数据库的数据。
  • 插入数据并处理任何错误。
  • 重新验证缓存并将用户重定向回发票页面。

创建新路线和表单

首先,在 /invoices 文件夹内,创建一个新的路径 /app/dashboard/invoices/create/page.tsx 文件。


在page.tsx上面写入代码

import Form from '@/app/ui/invoices/create-form';
import Breadcrumbs from '@/app/ui/invoices/breadcrumbs';
import { fetchCustomers } from '@/app/lib/data';

export default async function Page() {
    const customers = await fetchCustomers();

    return (
        <main>
            <Breadcrumbs
                breadcrumbs={[
                    { label: 'Invoices', href: '/dashboard/invoices' },
                    {
                        label: 'Create Invoice',
                        href: '/dashboard/invoices/create',
                        active: true,
                    },
                ]}
            />
            <Form customers={customers} />
        </main>
    );
}

这个组件的主要目的是提供一个创建发票的界面,包括导航和表单。它从服务器获取客户数据,然后将这些数据传递给表单组件,以便用户可以选择客户并创建新的发票。
这时候我们看到编辑器可能会显示import Form from '@/app/ui/invoices/create-form';,import Breadcrumbs from '@/app/ui/invoices/breadcrumbs';显示红色,因为我们还没有实现这块组件的逻辑。

编辑/app/ui/invoices/create-form 实现from表单提交

import Link from 'next/link';
import {
  CheckIcon,
  ClockIcon,
  CurrencyDollarIcon,
  UserCircleIcon,
} from '@heroicons/react/24/outline';
import { Button } from '@/app/ui/button';

export type CustomerField = {
  id: string;
  name: string;
};

export default function Form({ customers }: { customers: CustomerField[] }) {
  return (
    <form>
      <div className="rounded-md bg-gray-50 p-4 md:p-6">
        {/* Customer Name */}
        <div className="mb-4">
          <label htmlFor="customer" className="mb-2 block text-sm font-medium">
            Choose customer
          </label>
          <div className="relative">
            <select
              id="customer"
              name="customerId"
              className="peer block w-full cursor-pointer rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
              defaultValue=""
            >
              <option value="" disabled>
                Select a customer
              </option>
              {customers.map((customer) => (
                <option key={customer.id} value={customer.id}>
                  {customer.name}
                </option>
              ))}
            </select>
            <UserCircleIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500" />
          </div>
        </div>

        {/* Invoice Amount */}
        <div className="mb-4">
          <label htmlFor="amount" className="mb-2 block text-sm font-medium">
            Choose an amount
          </label>
          <div className="relative mt-2 rounded-md">
            <div className="relative">
              <input
                id="amount"
                name="amount"
                type="number"
                step="0.01"
                placeholder="Enter USD amount"
                className="peer block w-full rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
              />
              <CurrencyDollarIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
            </div>
          </div>
        </div>

        {/* Invoice Status */}
        <fieldset>
          <legend className="mb-2 block text-sm font-medium">
            Set the invoice status
          </legend>
          <div className="rounded-md border border-gray-200 bg-white px-[14px] py-3">
            <div className="flex gap-4">
              <div className="flex items-center">
                <input
                  id="pending"
                  name="status"
                  type="radio"
                  value="pending"
                  className="h-4 w-4 cursor-pointer border-gray-300 bg-gray-100 text-gray-600 focus:ring-2"
                />
                <label
                  htmlFor="pending"
                  className="ml-2 flex cursor-pointer items-center gap-1.5 rounded-full bg-gray-100 px-3 py-1.5 text-xs font-medium text-gray-600"
                >
                  Pending <ClockIcon className="h-4 w-4" />
                </label>
              </div>
              <div className="flex items-center">
                <input
                  id="paid"
                  name="status"
                  type="radio"
                  value="paid"
                  className="h-4 w-4 cursor-pointer border-gray-300 bg-gray-100 text-gray-600 focus:ring-2"
                />
                <label
                  htmlFor="paid"
                  className="ml-2 flex cursor-pointer items-center gap-1.5 rounded-full bg-green-500 px-3 py-1.5 text-xs font-medium text-white"
                >
                  Paid <CheckIcon className="h-4 w-4" />
                </label>
              </div>
            </div>
          </div>
        </fieldset>
      </div>
      <div className="mt-6 flex justify-end gap-4">
        <Link
          href="/dashboard/invoices"
          className="flex h-10 items-center rounded-lg bg-gray-100 px-4 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-200"
        >
          Cancel
        </Link>
        <Button type="submit">Create Invoice</Button>
      </div>
    </form>
  );
}

From表单上面的Button实现方式

import clsx from 'clsx';

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  children: React.ReactNode;
}

export function Button({ children, className, ...rest }: ButtonProps) {
  return (
    <button
      {...rest}
      className={clsx(
        'flex h-10 items-center rounded-lg bg-blue-500 px-4 text-sm font-medium text-white transition-colors hover:bg-blue-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 active:bg-blue-600 aria-disabled:cursor-not-allowed aria-disabled:opacity-50',
        className,
      )}
    >
      {children}
    </button>
  );
}

编辑import Breadcrumbs from '@/app/ui/invoices/breadcrumbs'组件,这个组件的主要目的是创建一个灵活的面包屑导航,可以根据传入的数据动态生成导航链接,并突出显示当前活动项。它提供了清晰的页面层次结构,帮助用户了解他们在网站中的位置。

import { clsx } from 'clsx';
import Link from 'next/link';
import { lusitana } from '@/app/ui/fonts';

interface Breadcrumb {
  label: string;
  href: string;
  active?: boolean;
}

export default function Breadcrumbs({
  breadcrumbs,
}: {
  breadcrumbs: Breadcrumb[];
}) {
  return (
    <nav aria-label="Breadcrumb" className="mb-6 block">
      <ol className={clsx(lusitana.className, 'flex text-xl md:text-2xl')}>
        {breadcrumbs.map((breadcrumb, index) => (
          <li
            key={breadcrumb.href}
            aria-current={breadcrumb.active}
            className={clsx(
              breadcrumb.active ? 'text-gray-900' : 'text-gray-500',
            )}
          >
            <Link href={breadcrumb.href}>{breadcrumb.label}</Link>
            {index < breadcrumbs.length - 1 ? (
              <span className="mx-3 inline-block">/</span>
            ) : null}
          </li>
        ))}
      </ol>
    </nav>
  );
}

当您点击提交按钮时,生成的链接http://172.16.100.104/dashboard/invoices/create?customerId=cc27c14a-0acf-4f4a-a6c9-d45682c144b9&amount=111&status=paid

  1. 表单提交:
  • 在 create-form.tsx 文件中,表单没有指定action属性。这意味着表单会默认提交到当前页面的 URL。
  1. 表单数据:
  • customerId: 从下拉菜单中选择的客户 ID
  • amount: 在输入框中输入的金额
  • status: 从单选按钮中选择的状态(pending 或 paid)
  1. 默认表单行为:
  • 当表单提交时,浏览器会将表单数据作为查询参数附加到当前 URL 上。
  1. URL 构成:
  • 基本 URL: http://172.16.100.104/dashboard/invoices/create
  • 查询参数: ?customerId=cc27c14a-0acf-4f4a-a6c9-d45682c144b9&amount=111&status=paid
  1. 参数解释:
  • customerId=cc27c14a-0acf-4f4a-a6c9-d45682c144b9: 选择的客户 ID
  • amount=111: 输入的金额
  • status=paid: 选择的发票状态

需要注意的是,这种 URL生成方式是浏览器的默认行为,而不是由React或Next.js直接控制的。在实际应用中,通常会使用 JavaScript 来处理表单提交,防止页面刷新并通过 API 发送数据。

以上内容为分页的详细技术实现步骤,如果你想看更多内容或者能够看到技术更新的内容,请百度搜索:曲速引擎 warp drive csdn 在首页找到我的地址访问即可,一线更新内容将会在我的个人博客上面更新,谢谢大家。

相关推荐

5款Syslog集中系统日志常用工具对比推荐

一、为何要集中管理Syslog?Syslog由Linux/Unix系统及其他网络设备生成,广泛分布于整个网络。因其包含关键信息,可用于识别网络中的恶意活动,所以必须对其进行持续监控。将Sys...

跨平台、多数据库支持的开源数据库管理工具——DBeaver

简介今天给大家推荐一个开源的数据库管理工具——DBeaver。它支持多种数据库系统,包括Mysql、Oracle、PostgreSQL、SLQLite、SQLServer等。DBeaver的界面友好...

强烈推荐!数据库管理工具:Navicat Premium 16.3.2 (64位)

NavicatPremium,一款集数据迁移、数据库管理、SQL/查询编辑、智能设计、高效协作于一体的全能数据库开发工具。无论你是MySQL、MariaDB、MongoDB、SQLServer、O...

3 年 Java 程序员还玩不转 MongoDB,网友:失望

一、什么场景使用MongoDB?...

拯救MongoDB管理员的GUI工具大赏:从菜鸟到极客的生存指南

作为一名在NoSQL丛林中披荆斩棘的数据猎人,没有比GUI工具更称手的瑞士军刀了。本文将带你围观五款主流MongoDB管理神器的特性与暗坑,附赠精准到扎心的吐槽指南一、MongoDBCompass:...

mongodb/redis/neo4j 如何自己打造一个 web 数据库可视化客户端?

前言最近在做neo4j相关的同步处理,因为产线的可视化工具短暂不可用,发现写起来各种脚本非常麻烦。...

solidworks使用心得,纯干货!建议大家收藏

SolidWorks常见问题...

统一规约-关乎数字化的真正实现(规范统一性)

尽管数字化转型的浪潮如此深入人心,但是,对于OPCUA和TSN的了解却又甚少,这难免让人质疑其可实现性,因为,如果缺乏统一的语义互操作规范,以及更为具有广泛适用的网络与通信,则数字化实际上几乎难以具...

Elasticsearch节点角色配置详解(Node)

本篇文章将介绍如下内容:节点角色简介...

产前母婴用品分享 篇一:我的母婴购物清单及单品推荐

作者:DaisyH8746在张大妈上已经混迹很久了,有事没事看看“什么值得买”已渐渐成了一种生活习惯,然而却从来没有想过自己要写篇文章发布上来,直到由于我产前功课做得“太过认真”(认真到都有点过了,...

比任何人都光彩照人的假期!水润、紧致的肌肤护理程序

图片来源:谜尚愉快的假期临近了。身心振奋的休假季节。但是不能因为这种心情而失去珍贵的东西,那就是皮肤健康。炙热的阳光和强烈的紫外线是使我们皮肤老化的主犯。因此,如果怀着快乐的心情对皮肤置之不理,就会使...

Arm发布Armv9边缘AI计算平台,支持运行超10亿参数端侧AI模型

中关村在线2月27日消息,Arm正式发布Armv9边缘人工智能(AI)计算平台。据悉,该平台以全新的ArmCortex-A320CPU和领先的边缘AI加速器ArmEthos-U85NPU为核心...

柔性——面向大规模定制生产的数字化实现的基本特征

大规模定制生产模式的核心是柔性,尤其是体现在其对定制的要求方面。既然是定制,并且是大规模的定制,对于制造系统的柔性以及借助于数字化手段实现的柔性,就提出了更高的要求。面向大规模定制生产的数字化业务管控...

创建PLC内部标准——企业前进的道路

作者:FrankBurger...

标准化编程之 ----------- 西门子LPMLV30测试总结

PackML乃是由OMAC开发且被ISA所采用的自动化标准TR88.00.02,能够更为便捷地传输与检索一致的机器数据。PackML的主要宗旨在于于整个工厂车间倡导通用的“外观和感觉”,...

取消回复欢迎 发表评论: