ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

RedwoodJS 表单构建实战:用 Form Helpers 告别受控组件样板代码

RedwoodJS 表单构建实战:用 Form Helpers 告别受控组件样板代码 RedwoodJS 表单构建实战用 Form Helpers 告别受控组件样板代码【免费下载链接】redwoodRedwoodGraphQL项目地址: https://gitcode.com/gh_mirrors/re/redwood本文以 RedwoodJS 官方教程第三章的 Building a Form 为核心系统讲解如何用redwoodjs/forms包提供的Form、TextField、TextAreaField、FieldError、Label、Submit等组件构建一个带验证、可样式化错误提示的 Contact Us 联系表单。读完本文你将掌握 RedwoodJS 表单的完整开发链路——从生成页面、配置路由到收集提交数据、声明式验证输入格式并能理解这些 Helpers 底层对 React Hook Form 的封装原理。为什么 Redwood 要自己做表单组件React 生态中构建表单向来是开发者的痛点Controlled Components 需要为每个输入框手工维护value与onChange状态Uncontrolled Components 又难以做精细验证第三方表单库则引入了额外的学习成本。HTML 规范最初的设想其实很简单一个带name属性的input字段点击按钮后把数据提交到某处。RedwoodJS 的方向是在不牺牲灵活性的前提下把表单开发拉回到这种简单直观的体验你不需要手写受控组件的管道代码验证和错误提示由框架自动处理。核心思路可以概括为三件事用Form包裹表单所有子字段自动注册到验证系统给字段传validation对象声明式描述验证规则用FieldError显示错误配合errorClassName/errorStyle实现完全可自定义的错误样式。这些组件全部来自redwoodjs/forms包其源码位于仓库的 packages/forms/src 目录底层构建在 React Hook Form 之上。如果 Helpers 不够用redwoodjs/forms还会把 React Hook Form 的所有导出原样转发如useForm、useFormContext你可以在需要时直接使用。第一步创建 Contact 页面并接入布局与路由我们以博客应用为例构建一个最简单的 Contact Us联系我们表单。首先用 Redwood CLI 生成页面yarn rw g page contact在布局的导航栏中加入入口为了让用户能访问到该页面在BlogLayout的nav中追加一个链接。注意要用 Redwood 路由器的Link和routes而不是原生a标签这样能享受路由名称带来的类型安全和自动 URL 生成import { Link, routes } from redwoodjs/router const BlogLayout ({ children }) { return ( header h1 Link to{routes.home()}Redwood Blog/Link /h1 nav ul li Link to{routes.home()}Home/Link /li li Link to{routes.about()}About/Link /li li Link to{routes.contact()}Contact/Link /li /ul /nav /header main{children}/main / ) } export default BlogLayoutTypeScript 版本需要在组件 props 上标注 children 类型import { Link, routes } from redwoodjs/router type BlogLayoutProps { children?: React.ReactNode } const BlogLayout ({ children }: BlogLayoutProps) { // ...同上 }在 Routes 中注册 Contact 路由接着在路由文件中把ContactPage放进与AboutPage、HomePage相同的Set wrap{BlogLayout}分组里这样它会自动被BlogLayout包裹与站点其他页面共享一致的导航结构import { Router, Route, Set } from redwoodjs/router import ScaffoldLayout from src/layouts/ScaffoldLayout import BlogLayout from src/layouts/BlogLayout const Routes () { return ( Router Set wrap{ScaffoldLayout} titlePosts titleToposts buttonLabelNew Post buttonTonewPost Route path/posts/new page{PostNewPostPage} namenewPost / Route path/posts/{id:Int}/edit page{PostEditPostPage} nameeditPost / Route path/posts/{id:Int} page{PostPostPage} namepost / Route path/posts page{PostPostsPage} nameposts / /Set Set wrap{BlogLayout} Route path/article/{id:Int} page{ArticlePage} namearticle / Route path/contact page{ContactPage} namecontact / Route path/about page{AboutPage} nameabout / Route path/ page{HomePage} namehome / /Set Route notfound page{NotFoundPage} / /Router ) } export default Routes由于 Contact 页面不涉及从数据库读取数据无需创建 Cell直接在页面组件里写表单即可。认识 Form Helpers从空表单到可提交Form一切表单的起点Redwood 表单从Form标签开始。它渲染一个真正的form元素同时为内部所有字段提供验证与错误上下文import { MetaTags } from redwoodjs/web import { Form } from redwoodjs/forms const ContactPage () { return ( MetaTags titleContact descriptionContact page / Form/Form / ) } export default ContactPage此时页面上什么都看不到空的form不占视觉空间接下来加入输入字段。TextField与Submit第一个可见的表单Redwood 内置了多种输入组件最常用的是渲染普通文本框的TextField。给它一个name属性——这是整个 Redwood 表单体系的关键约定后续验证规则、错误展示、提交数据收集全部以name作为关联键。再添加Submit渲染提交按钮import { MetaTags } from redwoodjs/web import { Form, TextField, Submit } from redwoodjs/forms const ContactPage () { return ( MetaTags titleContact descriptionContact page / Form TextField nameinput / SubmitSave/Submit /Form / ) } export default ContactPageTypeScript 下的导入方式相同组件签名自带泛型类型推断。此时点击 Save 页面不会报错但也看不到任何反馈——因为我们还没有处理提交数据。onSubmit拿到所有字段值与原生 HTML 表单类似Form接受onSubmit处理器但它收到的不是FormData而是一个包含所有已注册字段 name-value 对的对象这正是 Redwood 表单免手写受控组件的体验核心import { MetaTags } from redwoodjs/web import { Form, TextField, Submit } from redwoodjs/forms const ContactPage () { const onSubmit (data) { console.log(data) } return ( MetaTags titleContact descriptionContact page / Form onSubmit{onSubmit} TextField nameinput / SubmitSave/Submit /Form / ) } export default ContactPageTypeScript 用户可以享受完整类型收益定义FormValues接口描述表单结构然后用SubmitHandlerFormValues标注处理函数提交数据的类型即被静态校验import { MetaTags } from redwoodjs/web import { Form, TextField, Submit, SubmitHandler } from redwoodjs/forms interface FormValues { input: string } const ContactPage () { const onSubmit: SubmitHandlerFormValues (data) { console.log(data) } return ( MetaTags titleContact descriptionContact page / Form onSubmit{onSubmit} TextField nameinput / SubmitSave/Submit /Form / ) } export default ContactPage此时在浏览器中填写内容并点击 Save打开 Web Inspector 控制台就能看到以字段名为键的对象。扩充为真实表单TextAreaField与标签把单个输入框扩展为包含姓名、邮箱、留言三个字段的真实联系表单。多行文本字段使用TextAreaField它渲染 HTMLtextarea但同样带上了 Redwood 的表单能力import { MetaTags } from redwoodjs/web import { Form, TextField, TextAreaField, Submit } from redwoodjs/forms const ContactPage () { const onSubmit (data) { console.log(data) } return ( MetaTags titleContact descriptionContact page / Form onSubmit{onSubmit} TextField namename / TextField nameemail / TextAreaField namemessage / SubmitSave/Submit /Form / ) } export default ContactPage添加可读性标签用原生label配合htmlFor指向字段的nameForm onSubmit{onSubmit} label htmlFornameName/label TextField namename / label htmlForemailEmail/label TextField nameemail / label htmlFormessageMessage/label TextAreaField namemessage / SubmitSave/Submit /Form提交后控制台将输出包含name、email、message三个键的数据对象。验证从浏览器默认提示到完全可样式化任何表单都绕不开验证。有人留空怎么办格式填错怎么办Redwood 让这一切声明式完成。方式一原生required属性最直接的方式是给字段加上 HTML 标准的required属性TextField namename required / TextField nameemail required / TextAreaField namemessage required /提交空表单时浏览器会弹出原生提示。缺点在于这些浏览器默认提示无法被 CSS 样式化且文案不可控。方式二validation对象把required属性换成 Redwood 字段组件专属的validation属性传入对象形式TextField namename validation{{ required: true }} / TextField nameemail validation{{ required: true }} / TextAreaField namemessage validation{{ required: true }} /此时提交空表单浏览器同样会拦截第一个空字段获得焦点但这是通往完全自定义错误展示的踏脚石——下一步引入错误显示组件。FieldError声明式的错误展示FieldError是 Redwood 表单体系的点睛组件。它的name属性与对应输入字段的name保持一致从而知道该显示谁的错误。没有错误时它渲染为null不产生任何 DOM 输出有错误时才渲染一个span因此它是纯 HTML 元素可以随意样式化import { MetaTags } from redwoodjs/web import { FieldError, Form, TextField, TextAreaField, Submit } from redwoodjs/forms const ContactPage () { const onSubmit (data) { console.log(data) } return ( MetaTags titleContact descriptionContact page / Form onSubmit{onSubmit} label htmlFornameName/label TextField namename validation{{ required: true }} / FieldError namename / label htmlForemailEmail/label TextField nameemail validation{{ required: true }} / FieldError nameemail / label htmlFormessageMessage/label TextAreaField namemessage validation{{ required: true }} / FieldError namemessage / SubmitSave/Submit /Form / ) } export default ContactPage从源码看FieldError的实现相当精简见 packages/forms/src/FieldError.tsx它从 React Hook Form 的useFormContext中读取formState.errors按name取出对应的验证错误。若没有为某类验证提供自定义message则使用内置的默认文案模板例如required对应 is required、pattern对应 is not formatted correctly、minLength对应 is too shortconst DEFAULT_MESSAGES { required: is required, pattern: is not formatted correctly, minLength: is too short, maxLength: is too long, min: is too low, max: is too high, validate: is not valid, }所以渲染出的错误信息形如name is required。错误样式三件套className、errorClassName与errorStyle给FieldError加上classNameerror即可套用项目index.css中预置的.error类FieldError namename classNameerror /更进一步Redwood 的字段组件支持错误态样式切换正常时用className/style字段出错时自动切换为errorClassName/errorStyle。把错误态的样式直接写在输入框上让输入框本身变红Form onSubmit{onSubmit} label htmlFornameName/label TextField namename validation{{ required: true }} errorClassNameerror / FieldError namename classNameerror / label htmlForemailEmail/label TextField nameemail validation{{ required: true }} errorClassNameerror / FieldError nameemail classNameerror / label htmlFormessageMessage/label TextAreaField namemessage validation{{ required: true }} errorClassNameerror / FieldError namemessage classNameerror / SubmitSave/Submit /Form这一机制由 packages/forms/src/useErrorStyles.ts 中的useErrorStylesHook 实现当formState.errors中存在该字段或存在服务端错误时将className替换为errorClassName、style替换为errorStyle再作为 props 交给渲染的 HTML 元素。除了className/errorClassNamestyle与errorStyle也提供同样的切换能力。更完整的错误样式说明见仓库内 Forms 参考文档。Label让标签也跟随错误状态原生label无法感知错误态Redwood 为此提供了Label组件。注意它的name属性取代了原生htmlFor指向对应字段名这也是其他 Redwood 表单组件的一致约定import { MetaTags } from redwoodjs/web import { FieldError, Form, Label, TextField, TextAreaField, Submit } from redwoodjs/forms const ContactPage () { const onSubmit (data) { console.log(data) } return ( MetaTags titleContact descriptionContact page / Form onSubmit{onSubmit} Label namename errorClassNameerror Name /Label TextField namename validation{{ required: true }} errorClassNameerror / FieldError namename classNameerror / Label nameemail errorClassNameerror Email /Label TextField nameemail validation{{ required: true }} errorClassNameerror / FieldError nameemail classNameerror / Label namemessage errorClassNameerror Message /Label TextAreaField namemessage validation{{ required: true }} errorClassNameerror / FieldError namemessage classNameerror / SubmitSave/Submit /Form / ) } export default ContactPage从 packages/forms/src/Label.tsx 的源码可以看到Label渲染label htmlFor{name}同样借助useErrorStyles实现错误态样式切换当自闭合且无子元素时name文本会成为标签文字。:::info 即时清除错误反馈填写出错字段的内容时错误会立刻消失无需再次点击 Save 验证。这是基于 React Hook Form 默认在输入变化时重新校验的行为对用户和测试人员都是极佳的即时反馈体验。:::校验输入格式pattern与自定义错误文案required只保证非空。对于邮箱字段还应校验格式。validation属性完整接受 React Hook Formregister的选项其中pattern接受{ value, message }结构——value为正则表达式message为出错时显示给用户的文案TextField nameemail validation{{ required: true, pattern: { value: /^[^][^.]\..$/, message: Please enter a valid email address, }, }} errorClassNameerror /TypeScript 版本写法一致。注意这并非完备的邮箱校验正则例如无法处理所有合法邮箱形式教程中仅作为pattern用法的演示。如果你在pattern中不提供messageFieldError会回退到上文提到的默认文案 is not formatted correctly。触发时机用config{{ mode: onBlur }}提前验证默认情况下验证在提交时统一触发用户可能填完整个表单提交后才看到多个错误。更好的体验是用户一离开某个字段blur就立即校验把问题消灭在填写的当下。React Hook Form 的useForm支持通过mode配置验证触发时机Redwood 的Form通过config属性透传这些选项Form onSubmit{onSubmit} config{{ mode: onBlur }}config接受 React Hook FormuseForm的完整配置对象。除了onBlurReact Hook Form 还支持onChange、onTouched、all等模式可根据交互体验需要选择。源码视角Helpers 是如何工作的看完实战再从源码层面理解这套体系的运转机制会有更完整的认识。Form封装了 useForm、FormProvider 与服务端错误上下文packages/forms/src/Form.tsx 中Form内部做了三件事调用 React Hook Form 的useForm(config)把config原样传入用FormProvider把useForm返回的方法尤其是register注入上下文让任意层级的字段组件都能通过useFormContext取用——这是字段可以嵌套任意深度的原因将errorprop 中的 GraphQL 服务端错误graphQLErrors[0].extensions.properties.messages放入ServerErrorsContext供useErrorStyles读取以渲染服务端校验错误。提交时Form用formMethods.handleSubmit((data, event) onSubmit?.(data, event))包裹你的onSubmit因此只有验证通过时你的处理函数才会被调用提交数据以纯净的对象形式传入。如果你需要访问useForm返回的某些函数如reset可以自己调用useForm并通过formMethodsprop 传回Form字段注册依然正常工作。useRegister字段注册与强制namepackages/forms/src/useRegister.ts 中的useRegister是每个字段组件的核心。它从上下文取register函数把字段的name与validation注册进 React Hook Form并把onBlur、onChange包装为先执行验证系统逻辑、再执行用户传入的 prop。值得注意的是它强制要求nameif (!name) { throw Error(name prop must be provided) }所以 Redwood 表单字段的name是必填的——它既是数据收集的键也是验证与错误关联的键。此外useRegister会调用 packages/forms/src/coercion.ts 中的setCoercion处理类型转换如valueAsNumber、valueAsBoolean、valueAsJSON等并支持emptyAs自定义空值行为。字段组件家族按 HTML input type 自动生成除了教程中使用的TextField、TextAreaFieldredwoodjs/forms为几乎所有 HTML input 类型都提供了对应的TypeField组件。从 packages/forms/src/InputComponents.tsx 的源码可以看到一份完整的类型清单button, color, date, datetime-local, email, file, hidden, image, month, number, password, radio, range, reset, search, submit, tel, text, time, url, week它们共享同一套useErrorStylesuseRegister实现因此全部天然支持validation、errorClassName/errorStyle并做适当的默认类型转换例如CheckboxField默认valueAsBoolean、NumberField默认valueAsNumber。如果你想构建自定义字段组件也可以直接复用这两个 Hook把它们组合进自己的组件里。测试保障仓库在 packages/forms/src/tests/form.test.tsx 提供了表单包的行为测试覆盖字段注册、验证、错误展示等场景。教程项目的__fixtures__/test-project中还包含完整的 ContactPage 实现 与 BlogLayout可作为本教程成果的参考实现。小结与下一步回顾整个构建过程生成页面、配置布局与路由、用Form 字段组件搭起表单、用onSubmit收集数据、用validation声明规则、用FieldError与errorClassName/errorStyle/Label构建完全可样式化的错误反馈最后用config{{ mode: onBlur }}优化验证时机。整个过程几乎零手写状态管理代码。这套机制建立在 React Hook Form 之上因此其validation选项与config配置拥有远超本文示例的扩展能力更完整的组件列表、emptyAs空值处理、服务端错误展示FormError等细节可进一步阅读仓库内的 Forms 参考文档。当然一个联系表单的价值在于真正收到联系。教程的下一步是为提交的数据创建数据库表并编写第一个 GraphQL mutation——届时onSubmit中的data对象会直接作为 mutation 的输入变量提交到服务端表单体系也将迎来最后一个绝招。【免费下载链接】redwoodRedwoodGraphQL项目地址: https://gitcode.com/gh_mirrors/re/redwood创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表