网站首页 > 技术文章 正文
在上一章中,您使用 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
- 表单提交:
- 在 create-form.tsx 文件中,表单没有指定action属性。这意味着表单会默认提交到当前页面的 URL。
- 表单数据:
- customerId: 从下拉菜单中选择的客户 ID
- amount: 在输入框中输入的金额
- status: 从单选按钮中选择的状态(pending 或 paid)
- 默认表单行为:
- 当表单提交时,浏览器会将表单数据作为查询参数附加到当前 URL 上。
- URL 构成:
- 基本 URL: http://172.16.100.104/dashboard/invoices/create
- 查询参数: ?customerId=cc27c14a-0acf-4f4a-a6c9-d45682c144b9&amount=111&status=paid
- 参数解释:
- customerId=cc27c14a-0acf-4f4a-a6c9-d45682c144b9: 选择的客户 ID
- amount=111: 输入的金额
- status=paid: 选择的发票状态
需要注意的是,这种 URL生成方式是浏览器的默认行为,而不是由React或Next.js直接控制的。在实际应用中,通常会使用 JavaScript 来处理表单提交,防止页面刷新并通过 API 发送数据。
以上内容为分页的详细技术实现步骤,如果你想看更多内容或者能够看到技术更新的内容,请百度搜索:曲速引擎 warp drive csdn 在首页找到我的地址访问即可,一线更新内容将会在我的个人博客上面更新,谢谢大家。
猜你喜欢
- 2024-12-02 微信小程序跳转到第三方H5网页
- 2024-12-02 几行代码搞定 Spring Cloud OAuth2 授权码模式三个页面定制
- 2024-12-02 SpringMVC 04: SpringMVC中4种页面跳转方式
- 2024-12-02 实战:Springboot集成jsp页面报404四种解决方案
- 2024-12-02 从零开始的前端请求之旅:Fetch-API篇
- 2024-12-02 SpringBoot+Vue项目实战之前后端分离开发登录页面
- 2024-12-02 微信支付前后端实现(Vue+Spring Boot)
- 2024-12-02 三、Uni-app + vue3 页面如何跳转及传参?
- 2024-12-02 GitHub精选 | 快速搞定第三方授权登录
- 2024-12-02 https请求重定向到http的问题 (Nginx解决方式)
你 发表评论:
欢迎- 07-08记oracle日志挖掘实操&查询归档不正常增长情况(一)
- 07-08Oracle 伪列!这些隐藏用法你都知道吗?
- 07-08orcl数据库查询重复数据及删除重复数据方法
- 07-08重大故障!业务核心表被truncate删除,准备跑路……
- 07-08oracle数据恢复—oracle执行truncate命令误删除数据的数据恢复
- 07-08Oracle-rac 修改scanip(oracle 修改sequence cache)
- 07-08ORACLE RAC CDB和PDB切换(oracle数据库rac切换)
- 07-08Oracle rac haip作用(oracle rac的典型特征)
- 596℃几个Oracle空值处理函数 oracle处理null值的函数
- 590℃Oracle分析函数之Lag和Lead()使用
- 577℃0497-如何将Kerberos的CDH6.1从Oracle JDK 1.8迁移至OpenJDK 1.8
- 573℃Oracle数据库的单、多行函数 oracle执行多个sql语句
- 569℃Oracle 12c PDB迁移(一) oracle迁移到oceanbase
- 562℃【数据统计分析】详解Oracle分组函数之CUBE
- 549℃最佳实践 | 提效 47 倍,制造业生产 Oracle 迁移替换
- 542℃Oracle有哪些常见的函数? oracle中常用的函数
- 最近发表
-
- 记oracle日志挖掘实操&查询归档不正常增长情况(一)
- Oracle 伪列!这些隐藏用法你都知道吗?
- orcl数据库查询重复数据及删除重复数据方法
- 重大故障!业务核心表被truncate删除,准备跑路……
- oracle数据恢复—oracle执行truncate命令误删除数据的数据恢复
- Oracle-rac 修改scanip(oracle 修改sequence cache)
- ORACLE RAC CDB和PDB切换(oracle数据库rac切换)
- Oracle rac haip作用(oracle rac的典型特征)
- 新手小白怎么学UI设计 推荐学习路线是什么
- 超实用!0基础UI设计自学指南(0基础学ui设计好就业吗)
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端aes加密 (58)
- 前端脚手架 (56)
- 前端md5加密 (54)
- 前端路由 (61)
- 前端数组 (73)
- 前端js面试题 (50)
- 前端定时器 (59)
- 前端懒加载 (49)
- 前端获取当前时间 (50)
- 前端接口 (50)
- Oracle RAC (76)
- oracle恢复 (77)
- oracle 删除表 (52)
- oracle 用户名 (74)
- oracle 工具 (55)
- oracle 内存 (50)
- oracle 导出表 (57)
- oracle 中文 (51)
- oracle的函数 (57)
- 前端调试 (52)
本文暂时没有评论,来添加一个吧(●'◡'●)