解锁 React 中强大且现代的表单验证。本综合指南探讨了 experimental_useForm_Status 钩子、服务器操作和状态验证模式,用于构建健壮且高性能的表单。
使用 React 的 `experimental_useFormStatus` 掌握表单验证
表单是 Web 交互的基石。从简单的时事通讯注册到复杂的多步骤金融应用程序,它们是用户与我们的应用程序进行通信的主要渠道。然而,多年来,在 React 中管理表单状态一直是复杂性、样板代码和依赖疲劳的根源。我们一直在处理受控组件,与状态管理库作斗争,并编写无数的 `onChange` 处理程序,所有这些都是为了获得无缝且直观的用户体验。
React 团队一直在重新思考 Web 开发的这一基本方面,从而引入了一种新的、强大的范例,该范例以 React 服务器操作为中心。这种建立在渐进增强原则之上的新模型,旨在通过将逻辑移动到更接近它所属的位置(通常是服务器)来简化表单处理。这种客户端革命的核心是两个新的实验性钩子:`useFormState` 和我们今天讨论的重点 `experimental_useFormStatus`。
本综合指南将带您深入了解 `experimental_useFormStatus` 钩子。我们不仅会研究它的语法;我们还将探索它启用的思维模式:基于状态的验证逻辑。您将学习此钩子如何将 UI 与表单状态分离、简化待处理状态的管理,以及如何与服务器操作协同工作,从而创建即使在 JavaScript 加载之前也能工作的健壮、可访问且高性能的表单。准备好重新思考您所了解的关于在 React 中构建表单的所有知识。
范式转变:React 表单的演变
为了充分理解 `useFormStatus` 带来的创新,我们必须首先了解 React 生态系统中表单管理的历程。此背景突出了这种新方法优雅地解决的问题。
旧卫队:受控组件和第三方库
多年来,React 中表单的标准方法是 受控组件 模式。这包括:
- 使用 React 状态变量(例如,来自 `useState`)来保存每个表单输入的值。
- 编写一个 `onChange` 处理程序以在每次击键时更新状态。
- 将状态变量传递回输入的 `value` prop。
虽然这使 React 可以完全控制表单的状态,但它引入了大量的样板代码。对于具有十个字段的表单,您可能需要十个状态变量和十个处理函数。管理验证、错误状态和提交状态会增加更多的复杂性,通常会导致开发人员创建复杂的自定义钩子或使用全面的第三方库。
像 Formik 和 React Hook Form 这样的库通过抽象掉这种复杂性而声名鹊起。它们为状态管理、验证和性能优化提供了出色的解决方案。但是,它们代表了另一个需要管理的依赖项,并且通常完全在客户端上运行,这可能导致前端和后端之间重复的验证逻辑。
新时代:渐进增强和服务器操作
React 服务器操作引入了一种范式转变。核心思想是建立在 Web 平台的基础之上:标准 HTML `
一个简单的例子:智能提交按钮
让我们看看最常见的用例。我们将创建一个知道表单提交状态的组件,而不是标准的 ``。
文件: SubmitButton.js
import { experimental_useFormStatus as useFormStatus } from 'react-dom';
export function SubmitButton() {
const { pending } = useFormStatus();
return (
);
}
文件: SignUpForm.js
import { SubmitButton } from './SubmitButton';
import { signUpAction } from './actions'; // 一个服务器操作
export function SignUpForm() {
return (
在此示例中,`SubmitButton` 是完全独立的。它不接收任何 props。它使用 `useFormStatus` 来了解 `SignUpForm` 何时处于挂起状态,并自动禁用自身并更改其文本。这是一种强大的模式,用于解耦和创建可重用的、感知表单的组件。
问题的核心:基于状态的验证逻辑
现在我们来到了核心概念。`useFormStatus` 不仅用于加载状态;它是以不同方式思考验证的关键推动因素。
定义“状态验证”
基于状态的验证 是一种模式,其中验证反馈主要在响应表单提交尝试时传递给用户。主验证逻辑不是在每次击键时 (`onChange`) 或在用户离开字段时 (`onBlur`) 进行验证,而是在用户提交表单时运行。此提交的结果 - 它的 状态 (例如,成功、验证错误、服务器错误)- 然后用于更新 UI。
这种方法与 React 服务器操作完美对齐。服务器操作成为验证的单一真实来源。它接收表单数据,根据您的业务规则对其进行验证(例如,“此电子邮件是否已在使用?”),并返回一个结构化的状态对象,指示结果。
其合作伙伴的角色:`experimental_useFormState`
`useFormStatus` 告诉我们 什么 正在发生(挂起),但它没有告诉我们发生的事情的 结果 。为此,我们需要它的同级钩子:`experimental_useFormState`。
`useFormState` 是一个钩子,旨在根据表单操作的结果更新状态。它将操作函数和初始状态作为参数,并返回一个新状态和一个包装的操作函数以传递给您的表单。
const [state, formAction] = useFormState(myAction, initialState);
- `state`: 这将包含 `myAction` 上次执行的返回值。这是我们将获得错误消息的地方。
- `formAction`: 这是您的操作的一个新版本,您应该将其传递给 `` 的 `action` prop。当调用此函数时,它将触发原始操作并更新 `state`。
组合的工作流程:从点击到反馈
以下是 `useFormState` 和 `useFormStatus` 如何协同工作以创建完整的验证循环:
- 初始渲染:表单使用 `useFormState` 提供的初始状态进行渲染。不显示任何错误。
- 用户提交:用户单击提交按钮。
- 挂起状态:提交按钮中的 `useFormStatus` 钩子立即报告 `pending: true`。该按钮被禁用并显示一条加载消息。
- 操作执行:使用表单数据执行服务器操作(由 `useFormState` 包装)。它执行验证。
- 操作返回:该操作验证失败并返回一个状态对象,例如:
`{ message: "验证失败", errors: { email: "此电子邮件已被占用。" } }` - 状态更新:`useFormState` 接收此返回值并更新其 `state` 变量。这会触发表单组件的重新渲染。
- UI 反馈:表单重新渲染。来自 `useFormStatus` 的 `pending` 状态变为 `false`。该组件现在可以读取 `state.errors.email` 并在电子邮件输入字段旁边显示错误消息。
整个流程为用户提供了清晰的、服务器权威的反馈,完全由提交状态和结果驱动。
实践大师班:构建多字段注册表单
让我们通过构建一个完整的、生产风格的注册表单来巩固这些概念。我们将使用服务器操作进行验证,并使用 `useFormState` 和 `useFormStatus` 来创建出色的用户体验。
步骤 1:使用验证定义服务器操作
首先,我们需要我们的服务器操作。为了实现强大的验证,我们将使用流行的库 Zod。如果您使用的是像 Next.js 这样的框架,则此操作将位于一个单独的文件中,并标有 `'use server';` 指令。
文件: actions/authActions.js
'use server';
import { z } from 'zod';
// 定义验证模式
const registerSchema = z.object({
username: z.string().min(3, '用户名必须至少为 3 个字符长。'),
email: z.string().email('请输入有效的电子邮件地址。'),
password: z.string().min(8, '密码必须至少为 8 个字符长。'),
});
// 定义表单的初始状态
export const initialState = {
message: '',
errors: {},
};
export async function registerUser(prevState, formData) {
// 1. 验证表单数据
const validatedFields = registerSchema.safeParse(
Object.fromEntries(formData.entries())
);
// 2. 如果验证失败,则返回错误
if (!validatedFields.success) {
return {
message: '验证失败。请检查这些字段。',
errors: validatedFields.error.flatten().fieldErrors,
};
}
// 3. (模拟) 检查用户是否已存在于数据库中
// 在真实的应用程序中,您将在此处查询您的数据库。
if (validatedFields.data.email === 'user@example.com') {
return {
message: '注册失败。',
errors: { email: ['此电子邮件已注册。'] },
};
}
// 4. (模拟) 创建用户
console.log('正在创建用户:', validatedFields.data);
// 5. 返回成功状态
// 在真实的应用程序中,您可能会在此处使用来自 'next/navigation' 的 `redirect()` 进行重定向
return {
message: '用户注册成功!',
errors: {},
};
}
此服务器操作是我们表单的大脑。它是独立的、安全的,并为成功和错误状态提供了清晰的数据结构。
步骤 2:构建可重用的、状态感知的组件
为了保持我们主表单组件的整洁,我们将为我们的输入和提交按钮创建专用组件。
文件: components/SubmitButton.js
'use client';
import { experimental_useFormStatus as useFormStatus } from 'react-dom';
export function SubmitButton({ label }) {
const { pending } = useFormStatus();
return (
);
}
请注意 `aria-disabled={pending}` 的使用。这是一种重要的可访问性实践,可确保屏幕阅读器正确宣布禁用状态。
步骤 3:使用 `useFormState` 组装主表单
现在,让我们将所有内容集中在我们的主表单组件中。我们将使用 `useFormState` 将我们的 UI 连接到 `registerUser` 操作。
文件: components/RegistrationForm.js
{state.message} {state.message}
{state.errors.username[0]}
{state.errors.email[0]}
{state.errors.password[0]}
'use client';
import { experimental_useFormState as useFormState } from 'react-dom';
import { registerUser, initialState } from '../actions/authActions';
import { SubmitButton } from './SubmitButton';
export function RegistrationForm() {
const [state, formAction] = useFormState(registerUser, initialState);
return (
注册
{state?.message && !state.errors &&
现在,此组件是声明式的且整洁的。除了 `useFormState` 提供的 `state` 对象之外,它不管理任何状态。它的唯一工作是根据该状态渲染 UI。禁用按钮的逻辑封装在 `SubmitButton` 中,所有验证逻辑都位于 `authActions.js` 中。这种关注点分离对于可维护性来说是一个巨大的胜利。
高级技术和专业最佳实践
虽然基本模式很强大,但实际应用程序通常需要更多的细微差别。让我们探索一些高级技术。
混合方法:合并即时验证和提交后验证
基于状态的验证非常适合服务器端检查,但等待网络往返来告知用户其电子邮件无效可能会很慢。混合方法通常是最好的:
- 使用 HTML5 验证: 不要忘记基础知识!像 `required`、`type="email"`、`minLength` 和 `pattern` 这样的属性以零成本提供即时、浏览器本地反馈。
- 轻量级客户端验证: 对于纯粹的装饰性或格式化检查(例如,密码强度指示器),您仍然可以使用最少量的 `useState` 和 `onChange` 处理程序。
- 服务器端授权: 保留服务器操作用于最关键的、无法在客户端上完成的业务逻辑验证(例如,检查唯一用户名,根据数据库记录进行验证)。
这为您提供了两全其美的优势:针对简单错误的即时反馈和针对复杂规则的权威验证。
可访问性 (A11y):为每个人构建表单
可访问性是不容协商的。在实施基于状态的验证时,请记住以下几点:
- 宣布错误: 在我们的示例中,我们在错误消息容器上使用了 `aria-live="polite"`。这告诉屏幕阅读器一旦出现错误消息就立即宣布它,而不会中断用户当前的工作流程。
- 将错误与输入关联: 为了实现更强大的连接,请使用 `aria-describedby` 属性。输入可以指向其错误消息容器的 ID,从而创建一个程序化链接。
- 焦点管理: 在提交并出现错误后,请考虑以编程方式将焦点移动到第一个无效字段。这可以避免用户搜索出错的地方。
使用 `useFormStatus` 的 `data` 属性进行乐观 UI
想象一个社交媒体应用程序,用户在其中发布评论。您可以使应用程序感觉瞬间发生,而不是显示一秒钟的微调器。`useFormStatus` 中的 `data` 属性非常适合此目的。
提交表单后,`pending` 变为 true,并且 `data` 填充有提交的 `FormData`。您可以使用此 `data` 立即以临时的“待处理”视觉状态渲染新评论。如果服务器操作成功,您可以使用服务器的最终数据替换待处理的评论。如果失败,您可以删除待处理的评论并显示一个错误。这使得应用程序感觉非常灵敏。
驾驭“实验性”水域
解决 `experimental_useFormStatus` 和 `experimental_useFormState` 中的“实验性”前缀至关重要。
“实验性”的真正含义
当 React 将 API 标记为实验性时,这意味着:
- API 可能会更改: 名称、参数或返回值可能会在未来的 React 版本中更改,而不会遵循标准语义版本控制 (SemVer) 来进行重大更改。
- 可能存在错误: 作为一个新功能,它可能存在尚未完全理解或解决的边缘情况。
- 文档可能很稀疏: 虽然核心概念已记录在案,但有关高级模式的详细指南可能仍在不断发展。
何时采用,何时等待
那么,您应该在您的项目中使用它吗?答案取决于您的上下文:
- 适合: 个人项目、内部工具、初创公司或能够管理潜在 API 更改的团队。在像 Next.js 这样的框架中使用它(该框架已将这些功能集成到其 App Router 中)通常是一个更安全的选择,因为该框架可以帮助抽象掉一些变化。
- 谨慎使用于: 大型企业应用程序、任务关键型系统或具有长期维护合同的项目,在这些项目中,API 稳定性至关重要。在这些情况下,等到钩子升级为稳定的 API 可能是明智之举。
始终关注 React 官方博客和文档,以获取有关这些钩子稳定化的公告。
结论:React 中表单的未来
`experimental_useFormStatus` 及其相关 API 的引入不仅仅是一个新工具;它代表了我们如何使用 React 构建交互式体验的哲学转变。通过拥抱 Web 平台的基础并将有状态逻辑共置在服务器上,我们可以构建更简单、更具弹性和通常性能更高的应用程序。
我们已经看到了 `useFormStatus` 如何为组件提供一种干净、解耦的方式来响应表单提交的生命周期。它消除了待处理状态的 prop drilling,并启用了优雅的、独立的 UI 组件,例如智能 ``。当与 `useFormState` 结合使用时,它解锁了基于状态的验证的强大模式,其中服务器是最终权威,客户端的主要责任是渲染服务器操作返回的状态。
虽然“实验性”标签需要一定程度的谨慎,但方向是明确的。React 中表单的未来是渐进增强、简化的状态管理以及客户端和服务器逻辑之间强大的、无缝的集成。通过今天掌握这些新钩子,您不仅仅是在学习一个新 API;您正在为使用 React 进行的下一代 Web 应用程序开发做准备。