资讯详情

资讯详情

React Router 表单校验实战:基于 action 与 useFetcher 的注册表单实现指南

React Router 表单校验实战基于 action 与 useFetcher 的注册表单实现指南【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router表单校验是几乎所有 Web 应用绕不开的核心交互。在 React Router本文基于GitHub_Trending/re/react-router仓库当前的 framework 与 data 两种路由模式中服务端action可以像 Web 标准表单那样解析提交数据、校验并将错误回传给 UI而useFetcher能让我们在不触发页面导航的前提下完成这一整套流程。本文以“注册Sign Up表单”为例带你完整走通从路由配置、action 校验、以 400 状态码回传错误到前端渲染校验信息的整条链路并结合仓库源码解释其中每个机制为何如此工作。该指南适用于 React Router 的 framework全栈框架与 data仅使用数据路由能力两种模式。校验逻辑的薄厚取舍可以自行决定——你可以将它只作为动作管线的最小骨架再叠加你偏爱的第三方校验库和错误组件。1. 环境准备注册一条 Signup 路由假设项目已经初始化好了 React Router 的应用骨架包含app/目录与基于 routes.ts 约定的路由配置文件。首先在路由配置中声明一条signup路由指向负责注册页面的模块文件import { type RouteConfig, route, } from react-router/dev/routes; export default [ route(signup, signup.tsx), ] satisfies RouteConfig;其中react-router/dev/routes提供类型化的route()辅助函数与RouteConfig类型route(signup, signup.tsx)表示 URL 路径/signup由同目录下的signup.tsx模块遵循文件路由约定默认位于app/下渲染。satisfies RouteConfig会让 TypeScript 在配置不符合路由定义规范时立即给出编译期错误防止把错误的路由配置悄悄带入运行时。接下来创建最小可提交的注册表单。这里使用useFetcher提供的fetcher.Form组件而不是普通form或Form因为后文我们会让表单与action交互时不发生路由跳转import type { Route } from ./types/signup; import { useFetcher } from react-router; export default function Signup(_: Route.ComponentProps) { let fetcher useFetcher(); return ( fetcher.Form methodpost p input typeemail nameemail / /p p input typepassword namepassword / /p button typesubmitSign Up/button /fetcher.Form ); }几点说明./types/signup是 React Router 在 dev 模式或类型生成阶段自动产出的类型模块。Route.ComponentProps、Route.ActionArgs、Route.LoaderArgs等都从这里按需取用具体机制参见 route-module 类型安全指南。fetcher.Form methodpost与传统表单的关键差异在于提交时navigate: false数据只会发给当前路由的action不会触发地址栏变更或页面级导航。想深入理解 navigational Form 与 fetcher.Form 的区别可阅读 fetch 与 Form 的使用时机及 form-vs-fetcher 背景说明。表单项通过name属性参与序列化服务端将按这些name读取字段值。2. 定义服务端 action解析表单、校验并回传错误在同一个signup.tsx模块中导出action函数。本指南侧重于讲清楚表单校验涉及的机制提交数据如何流转、校验失败如何返回、状态码起什么作用而不是深入某个具体校验规则库或错误对象结构因此下面只对 email 与 password 做最基本的规则检查import type { Route } from ./types/signup; import { redirect, useFetcher, data } from react-router; export default function Signup(_: Route.ComponentProps) { // omitted for brevity } export async function action({ request, }: Route.ActionArgs) { const formData await request.formData(); const email String(formData.get(email)); const password String(formData.get(password)); const errors {}; if (!email.includes()) { errors.email Invalid email address; } if (password.length 12) { errors.password Password should be at least 12 characters; } if (Object.keys(errors).length 0) { return data({ errors }, { status: 400 }); } // Redirect to dashboard if validation is successful return redirect(/dashboard); }2.1 三个关键动作逐一拆解读取表单数据。request来自Route.ActionArgs是标准的 WebRequest对象await request.formData()按multipart/form-data/application/x-www-form-urlencoded的标准解析请求体返回一个FormData。接着用formData.get(email)/get(password)按表单控件的name取值并统一包一层String()把null字段缺失等情况收敛成可比较的字符串。整套动作与原生 Fetch 语义完全一致无框架黑魔法。校验并把错误组织成对象。示例把每个字段的错误文本存入errors对象errors.email、errors.password。实际项目里这里可以替换为 zod / valibot 等第三方校验库的返回值本指南只关心 React Router 负责的“传输与状态”部分。通过data()携带状态码返回。data({ errors }, { status: 400 })是失败路径的返回值——它把错误数据与一个 400Bad Request状态码捆绑返回给发起请求的 fetcher。从源码可以看到data()在 router/utils.ts 中返回一个DataWithResponseInit包装export function dataD(data: D, init?: number | ResponseInit) { return new DataWithResponseInit( data, typeof init number ? { status: init } : init, ); }也就是说它既允许传数字状态码内部自动包装为{ status: init }也允许传完整的ResponseInit自定义 header、其他状态码等。该工具同时从 server-runtime/single-fetch.ts 作为服务端响应工具导出是 framework 模式下 action/loader 返回结构化数据可包含Date、Map、Error、Promise等可序列化类型的入口之一。官方 API 细节见 data() 工具文档。校验成功则重定向。return redirect(/dashboard)让用户跳转到仪表盘页属于标准的“PRGPost-Redirect-Get”收尾。2.2 为什么校验失败要用 400 而不是 200这段代码里data({ errors }, { status: 400 })中的status: 400绝非可有可无语义上400 是 Web 标准中表示“请求本身有误Bad Request”的通用状态码用它向客户端传达“这不是服务器故障而是你提交的数据需要修正”也便于前端错误日志、监控与可观测性系统分类。机制上React Router 中 action 成功后默认会触发一次数据重新校验revalidation让loader的最新数据回流到 UI。而这条逻辑对 4xx/5xx 的响应是“关掉的”。其实现位于 router/router.ts// Dont revalidate loaders by default after action 4xx/5xx responses // when the flag is enabled. They can still opt-into revalidation via // shouldRevalidate via actionResult let actionStatus pendingActionResult ? pendingActionResult[1].statusCode : undefined; let shouldSkipRevalidation actionStatus actionStatus 400;代码注释与该逻辑共同说明当 action 返回 400 这类状态码时路由会跳过默认的 loader 重新验证因为数据没有发生任何合法变更没有必要为了“展示错误”而重跑一遍 loader。反过来说只有当返回 2xx或发生 3xx 重定向时action 才会按正常流程触发数据重验证并继续导航/更新逻辑。这就是“用 400 隔离校验失败与正常提交”的底层原因。2.3 这条链路的服务端执行概况在 framework 模式下action 请求由服务端处理管线接管。server-runtime/single-fetch.ts 中的singleFetchAction会在执行前做 CSRF 防护检查throwIfPotentialCSRFAttack随后把请求交给staticHandler.query完成 action 路由匹配与执行。这意味着你写的 action 并不是被“随手调用”的而是与 loader、middleware 一样被纳入统一的数据策略与请求管线相关机制参见 client-data 指南与>export default function Signup(_: Route.ComponentProps) { let fetcher useFetcher(); let errors fetcher.data?.errors; return ( fetcher.Form methodpost p input typeemail nameemail / {errors?.email ? em{errors.email}/em : null} /p p input typepassword namepassword / {errors?.password ? ( em{errors.password}/em ) : null} /p button typesubmitSign Up/button /fetcher.Form ); }要点如下fetcher.data保存最近一次由该 fetcher 发起的 action/loader 成功返回的数据。当 action 返回data({ errors }, { status: 400 })时虽然状态码是 400但这属于“业务性错误响应”会被正常交给 fetcher 而非抛给错误边界因此fetcher.data?.errors能拿到错误对象。用errors?.email、errors?.password做存在性判断存在则就地渲染em错误文本不存在则渲染nullUI 保持干净。校验通过时 action 走redirect(/dashboard)页面随之导航离开这里的错误展示逻辑自然不再可见。3.1 深入fetcher.Form 的“不导航”承诺从何而来回顾 dom/lib.tsx 中FetcherForm的实现let FetcherForm React.forwardRefHTMLFormElement, FetcherFormProps( (props, ref) { return ( Form {...props} navigate{false} fetcherKey{fetcherKey} ref{ref} / ); }, ); FetcherForm.displayName fetcher.Form;fetcher.Form本质上是给普通Form强制注入了navigate{false}与本次调用唯一的fetcherKey。再结合useFetcher在 dom/lib.tsx 中的定义可以看到未显式传入key时fetcher 使用React.useId()生成组件私有的默认 key传入key则可以在应用任意位置共享同一个 fetcher 的状态。组件挂载时在 router 中注册 fetcher卸载时通过useEffect清理避免内存泄漏。fetcher 自带独立状态机fetcher.state取值为idle | loading | submittingsubmitting期间表单可显示 pending UIfetcher.data存放返回数据。可通过 useFetcher 官方文档 查看完整签名。因此你可以在同一个页面放多个fetcher.Form各自提交互不干扰、互不引发页面跳转——这正是复杂动态界面搜索联想、行内编辑、批量操作需要的交互模型。3.2 错误展示位置的权衡fetcher.data 与 action 数据再校验本示例把错误直接渲染在触发提交的组件里因为错误源与展示源在同一个 fetcher 作用域内。若你的错误需要渲染在“别的路由/别的组件”例如表单挂在 layout 上、错误提示要在别处显示React Router 还提供useActionData()等机制与 fetcher 体系配合同时错误展示组件也要考虑 Error Boundary 的兜底非 4xx 校验类错误之外的渲染错误交由 error-boundary 指南 处理这些都属于在基础链路上的进阶组合。4. 完整链路回顾与可扩展方向至此这条表单校验链路是自洽的用户提交fetcher.Form methodpost请求以navigate: false的方式进入signup路由的action不发生页面导航action 用request.formData()读取字段、执行校验有错误时data({ errors }, { status: 400 })返回——4xx 状态既符合 Web 语义又让 React Router 跳过无意义的 loader 重新验证fetcher.data承接错误数据组件读取fetcher.data?.errors就地渲染错误校验通过则redirect(/dashboard)完成注册跳转并触发正常的数据重新验证。在此基础上你还可以自然地扩展均不影响本文所述的骨架机制引入第三方校验库把手工if检查替换为 zod/valibot 的 schema 校验错误结构归一后仍用data({ errors }, { status: 400 })返回按字段保留用户输入校验失败时让表单保留用户已填内容仅标记错误字段避免用户重填与路由 action 体系打通若你更希望整页导航 通过useActionData读取错误可参考 actions 入门同一份 action 返回值对两种读取方式都成立类型安全action 的返回值经data()后会被类型推断到 fetcher/useActionData侧配合 route-module 类型安全指南 可以让errors.email等访问在编译期即被检查避免手写as断言带来的漂移风险。一条覆盖“提交 → 服务端校验 → 错误回传 → 页面呈现 → 成功跳转”的注册流程在 React Router 中就这样以接近原生 Web 语义的方式完成了。【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →