ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Redwood Forms 完全指南:基于 React Hook Form 的声明式表单开发

Redwood Forms 完全指南:基于 React Hook Form 的声明式表单开发 后端前端Web框架开发工具【免费下载链接】redwoodRedwoodGraphQL项目地址https://gitcode.com/gh_mirrors/re/redwood点击查看免费下载Redwood 框架在redwoodjs/forms中提供了一套开箱即用的表单辅助组件它们是对 React Hook Form 包的源码实现系统讲解Form、Label、各输入字段、FormError、FieldError、SelectField的用法并深入剖析 Redwood 独有的类型强制转换coercion与空值处理emptyAs机制帮助你写出可直接对接 GraphQL 类型系统的健壮表单。概述Redwood 的表单辅助组件Redwood 的表单能力全部位于redwoodjs/forms包中。它的定位非常明确在大多数场景下让 React Hook Form 用起来更简单。如果这些辅助组件不够灵活你可以直接使用 React Hook Form 本身——redwoodjs/forms会原样导出 RHF 的全部 APIimport { useForm, useFormContext, /** * Or anything else React Hook Form exports! * * see {link https://react-hook-form.com/api} */ } from redwoodjs/forms这一点在 packages/forms/src/index.tsx 中得到了源码级印证export * from react-hook-form出现在该文件第一行紧随其后才导出 Redwood 自己的组件与钩子。redwoodjs/forms导出的核心组件如下组件说明Form包裹所有表单组件提供表单上下文form context与错误上下文error contextFormError展示服务端返回的错误消息通常放在表单顶部Label替代 HTMLlabel标签支持错误样式相关 propsInputField替代 HTMLinput标签支持校验与错误样式 props其余输入组件见下文列表SelectField替代 HTMLselect标签支持校验与错误样式 propsTextAreaField替代 HTMLtextarea标签支持校验与错误样式 propsFieldError当name相同的字段存在校验错误时展示错误消息仅在关联字段有错误时渲染Submit替代button typesubmit触发校验与提交执行传给Form的onSubmit函数所有 HTMLinput类型也都有对应的命名组件遵循TypeField命名约定Type为 HTML input type。全文统称它们为输入字段input fields完整列表为ButtonField、CheckboxField、ColorField、DateField、DatetimeLocalField、EmailField、FileField、HiddenField、ImageField、MonthField、NumberField、PasswordField、RadioField、RangeField、ResetField、SearchField、SubmitField、TelField、TextField、TimeField、UrlField、WeekField这些命名组件并不是手写的而是由 packages/forms/src/InputComponents.tsx 中一段 JS 元编程metaprogramming批量生成的源码维护了一个INPUT_TYPES数组button、color、date、datetime-local、email、file、hidden、image、month、number、password、radio、range、reset、search、submit、tel、text、time、url、week然后通过pascalcase(type) Field动态创建组件每个组件都只是把固定的type透传给InputField。这也解释了为什么命名组件与InputField type...本质上等价——只是前者在类型valueAs等默认配置上更省心。校验与错误样式 Props所有以Field结尾的组件即所有输入字段以及SelectField和TextAreaField都接受三类核心 propsProp说明name字段名。React Hook Form 用它作为 key 把字段与整个表单体系关联起来validation全部校验逻辑。接受 React Hook Form 的全部registeroptions外加 Redwood 独有的强制转换辅助valueAsBoolean、valueAsJSONerrorClassName字段存在错误时应用的 class 名errorStyle字段存在错误时应用的 style除name外传给组件的其他所有 props 都会被转发到其渲染出的 HTML 标签上。从类型定义看这一约定被固化在 packages/forms/src/FieldProps.ts 的FieldProps接口中它统一声明了name、id、emptyAs、errorClassName、errorStyle、className、style、validation、type、onBlur、onChange作为所有字段组件的单一事实来源source of truth。而错误样式与校验的实际接线分别由useErrorStyles和useRegister两个内部钩子完成下文详述。典型示例一个使用这些辅助组件的典型 React 组件长这样import { Form, Label, TextField, TextAreaField, FieldError, Submit, } from redwoodjs/forms const ContactPage () { const onSubmit (data) { console.log(data) } return ( Form onSubmit{onSubmit} Label namename classNamelabel errorClassNamelabel error / TextField namename classNameinput errorClassNameinput error validation{{ required: true }} / FieldError namename classNameerror-message / Label nameemail classNamelabel errorClassNamelabel error / TextField nameemail classNameinput errorClassNameinput error validation{{ required: true, pattern: { value: /[^][^\.]\../, }, }} / FieldError nameemail classNameerror-message / Label namemessage classNamelabel errorClassNamelabel error / TextAreaField namemessage classNameinput errorClassNameinput error validation{{ required: true }} / FieldError namemessage classNameerror-message / Submit classNamebuttonSave/Submit /Form ) }这段代码展示了三个核心编排规律Label的name必须与输入字段的name一致用于关联每个输入字段同时提供正常样式与错误样式className/errorClassName、style/errorStyle成对出现FieldError紧跟对应输入字段用于就地展示该校验失败时的消息。Form组件详解任何希望 Redwood 进行校验与错误样式化的表单都应被Form包裹。PropsProp说明config接受一个对象内含 React Hook Form 的useFormhook 的全部选项例如config{{ mode: onBlur }}可在失焦时触发校验formMethodsuseForm返回的函数集合。仅当你需要访问useForm返回的某个函数如reset时才需要传此 prop见下文示例onSubmit校验成功后调用的函数参数为包含表单所有字段 name-value 对的对象其余所有 props 都会被转发到它渲染出的form标签上。此外Form还接受一个未写入上表的errorprop——它会被传递给内部的ServerErrorsContext用于在服务端返回错误时让相关字段自动带上错误样式见下文错误样式背后的机制。Form的工作原理Form封装了三样东西React Hook Form 的useFormhook、FormProvider上下文以及 Redwood 的ServerErrorsContext。要理解它需要先理解 React Hook Form 的运作机制。useForm是 React Hook Form 的核心 hook它返回一组函数其中register用于把字段注册进表单体系以便校验这涉及受控组件与非受控组件的取舍React Hook Form 采用后者。Redwood 的所有表单辅助组件都需要register才能工作但它们可能被嵌套在任意深度无法直接拿到register——这正是FormProvider存在的意义通过把useForm返回的函数传给FormProviderRedwood 的辅助组件就能通过useFormContext取到自己需要的东西。源码 packages/forms/src/Form.tsx 清晰地展示了这一组装过程FormInner内部先调用useFormTFieldValues(config)得到hookFormMethods再以propFormMethods || hookFormMethods的方式决定使用外部传入的formMethods还是内部创建的实例随后把onSubmit交给formMethods.handleSubmit包装保证只有校验通过才触发最后用ServerErrorsContext.Provider包裹FormProvider把服务端错误消息按字段名注入上下文。使用formMethodsuseForm返回的某些函数例如重置表单字段的reset非常实用但只有自己调用useForm才能拿到。此时仍需把useForm的返回值传给Form由它转发给FormProviderRedwood 的辅助组件才能正常注册import { useForm } from react-hook-form const ContactPage () { const formMethods useForm() const onSubmit (data) { console.log(data) formMethods.reset() } return ( Form formMethods{formMethods} onSubmit{onSubmit} {/* Still works! */} TextField namename validation{{ required: true }} / /Form ) }注意这里useForm可以直接从react-hook-form导入也可以从redwoodjs/forms导入该包原样转发了 RHF 的全部导出。FormError服务端错误展示FormError会渲染一个div包含一个标题消息和一个ul列表枚举服务端保存表单时返回的错误。如果表单通过了客户端校验但服务端仍拒绝例如 GraphQL 类型校验失败它就会展示错误。在 scaffold 生成的页面中提交一个骗过客户端校验的表单即可看到它的实际效果。典型场景表单中有一个TextField用于输入邮箱地址但你没有为它配置任何校验import { useMutation } from redwoodjs/web const CREATE_CONTACT gql mutation CreateContactMutation($input: ContactInput!) { createContact(input: $input) { id } } const ContactPage () { const [create, { loading, error }] useMutation(CREATE_CONTACT) const onSubmit (data) { create({ variables: { input: data }}) } return ( Form onSubmit{onSubmit} FormError error{error} / {/* No validation—any email goes! */} TextField nameemail / /Form ) }由于没有客户端校验任何内容都能通过——至少客户端是这样。但 GraphQL 建立在类型系统之上不会轻易放行它会抛出错误并通过useMutationhook 返回的error对象冒泡到顶层FormError据此渲染出类似内容div p Cant create new contact: /p ul li email is not formatted like an email address /li /ul /div从源码 packages/forms/src/FormError.tsx 可以看到它的错误解析逻辑当 GraphQL 错误存在时取graphQLErrors[0].message作为根消息若extensions.code BAD_USER_INPUTServiceValidation 错误则把标题覆盖为 Errors prevented this form from being saved并从extensions.properties.messages中逐条展开字段错误当没有 GraphQL 错误而有网络错误时则从networkError.bodyText或networkError.result.errors中提取消息。渲染上支持对 wrapper、title、list、listItem 分别配置className与style例如wrapperClassName、titleClassName、listClassName、listItemClassName。Label组件Label渲染一个 HTMLlabel标签并根据其关联字段是否校验失败应用不同的className和style。该标签可以自闭合此时name会成为标签文本Label namename classNameinput errorClassNameinput error / !-- Renders: label forname classinputname/label --也可以使用标准的开闭标签并传入文本此时文本成为渲染出的label的文本内容Label namename classNameinput errorClassNameinput errorYour Name/Label !-- Renders: label forname classinputYour Name/label --除下表列出的 props 外其余 props 全部透传给底层label标签Prop说明name该标签关联的字段名应与输入字段的nameprop 相同errorClassName同name字段存在校验错误时使用的classNameerrorStyle同name字段存在校验错误时使用的style输入字段Input Fields输入是大多数表单的主干。虽然可以只用InputField配合typeprop 做出各种输入框但更推荐使用上面列出的命名输入字段——它们针对特定类型配置了合适的默认行为例如强制类型转换。默认强制转换Default coercion某些输入字段会自动进行类型转换但你始终可以在validationprop 的setValueAs属性中覆盖它对没有内置转换的字段也可以通过setValueAs手动设置。自动进行类型转换的字段字段默认转换CheckboxFieldvalueAsBooleanNumberFieldvalueAsNumberDateFieldvalueAsDateDatetimeLocalFieldvalueAsDatevalueAsDate与valueAsNumber内置于 React Hook Form基于 HTML 标准。但由于 Redwood 后端使用 GraphQL表单提交的类型必须与 GraphQL 服务端期望的类型一致。为此Redwood 在 RHF 的valueAs属性基础上扩展了两个便利属性避免用户大量使用setValueAs手写自定义转换valueAsBooleanvalueAsJSON空输入值的默认处理Redwood 对空输入字段值提供了灵活的处理策略正确处理空字段能极大方便数据库关联字段relation fields的建模。空字段值的行为由以下规则决定如果用户指定了setValueAs则由该函数决定空字段的行为如果设置了emptyAsprop则emptyAs决定空字段的值emptyAs可选值见下节如果设置了validation { required: true }空字段将返回null——但 React Hook Form 的校验会拦截提交因为空值不满足required如果字段是 Id 字段即其 name 以 Id 结尾空字段返回null。null对大多数数据库关联字段而言是最恰当的值如需其他值请使用emptyAsprop如果以上情况都不适用空字段场景下的值按如下规则设置DateFields → nullNumberFields → NaN设置了 valueAsNumber 的 TextFields → NaN设置了 valueAsNumber 的 SelectFields → NaN未设置 valueAsNumber 的 SelectFields → 空字符串设置了 valueAsJSON 的 TextFields → nullTextFields 及类似字段 → 空字符串这套决策链在 packages/forms/src/coercion.ts 中有逐条对应的注释与实现setCoercion函数会先判断用户是否提供了setValueAs提供则直接返回交给用户函数随后根据valueAsBoolean/valueAsJSON/类型与valueAsDate/valueAsNumber推断出valueAs类别valueAsDate、valueAsJSON、valueAsNumber、valueAsString之一再从SET_VALUE_AS_FUNCTIONS表中按emptyAs取值选择对应的转换函数。表中为每种类型都预置了emptyAsNull、emptyAsUndefined、emptyAsZero、emptyAsString等变体例如valueAsNumber的emptyAsNaN返回isValueEmpty(val) ? NaN : val而valueAsDate的emptyAsNull返回isValueEmpty(val) ? null : new Date(val)。值得注意的是源码中valueAsBoolean一栏被整体注释掉了coercion.ts原因是 React Hook Form 目前不支持 checkbox 的setValueAs功能——这解释了文档中CheckboxField默认valueAsBoolean与checkbox 暂不支持emptyAs之间的微妙关系checkbox 的布尔转换由 RHF 对 checkbox 类型的原生处理完成而非 Redwood 的setValueAs管道。此外当设置valueAsJSON时setCoercion还会注入一个JSONValidation校验函数coercion.tsJSON.parse失败时返回NaN作为非法 JSON哨兵值该校验函数据此拒绝提交——这意味着TextField validation{{ valueAsJSON: true }}自带 JSON 合法性校验。emptyAspropemptyAsprop 允许用户在未指定setValueAs的情况下覆盖输入字段为空时的默认返回值。可选值nullundefined0空字符串例如NumberField namequantity emptyAsundefined / NumberField namescore emptyAs{null} /字段为空时分别返回undefined与null。从类型定义看EmptyAsValue null | undefined | 0 | coercion.ts它与RedwoodRegisterOptions一同从包中导出index.tsx便于自定义组件复用。自定义输入字段useRegister与useErrorStyles你可以通过 Redwood 的useRegister和useErrorStyles两个 hook 创建与 Redwood 体系无缝集成的自定义字段二者各司其职useRegister把字段注册进 React Hook Form是 RHFregister的封装。源码 packages/forms/src/useRegister.ts 显示它从useFormContext取出register在调用前先执行setCoercion注入 Redwood 的转换逻辑并把onBlur/onChange包装为先执行 RHF 的默认处理再调用用户传入的处理器最后合并ref转发——因此返回对象可以直接展开到任意input/select/textarea上useErrorStyles为自定义字段搭建错误样式。源码 packages/forms/src/useErrorStyles.ts 实现了一件事从useFormContext读取formState.errors从ServerErrorsContext读取服务端错误若存在服务端错误则通过setError(name, { type: server, message })把它注册进表单状态这样FieldError也能展示它随后若validationError存在就用errorClassName/errorStyle覆盖className/style并返回。两者配合即可创建既复刻 Redwood 输入字段行为、又承载自定义业务逻辑的字段。下面是一个集标签、输入框、错误展示于一体的必填自定义字段示例import { FieldError, useErrorStyles, useRegister } from redwoodjs/forms const RequiredField ({ label, name, validation }) { const register useRegister({ name, validation: {...validation, required: true} }) const { className: labelClassName, style: labelStyle } useErrorStyles({ className: my-label-class, errorClassName: my-label-error-class, name, }) const { className: inputClassName, style: inputStyle } useErrorStyles({ className: my-input-class, errorClassName: my-input-error-class, name, }) return ( label className{labelClassName} style{labelStyle}{label}/label input className{inputClassName} style{inputStyle} typetext {...register} / FieldError name{name} / / ) }受控组件字段Controlled Component Fields如果你正在使用功能完整的组件库或拥有自研的生产级组件可以通过 Redwood 表单的useErrorStyleshook 与 React Hook Form 的Controller组件无缝集成。下面示例展示了如何集成primereact的ToggleButton使其像上述命名输入字段一样用于 Redwood 表单import { ToggleButton } from primereact/togglebutton import type { ToggleButtonProps } from primereact/togglebutton import { Controller, RegisterOptions, useErrorStyles } from redwoodjs/forms interface Props extends ToggleButtonProps { validation?: RegisterOptions errorClassName?: string } const ToggleButtonField (props: Props) { const { name, className, errorClassName, defaultValue, validation, style, ...propsRest } props const { className: componentClassName, style: componentStyle } useErrorStyles({ className: className, errorClassName: errorClassName, name: name, }) return ( Controller name{name} defaultValue{defaultValue} rules{validation} render{({ field: { onChange, onBlur, value, name, ref } }) ( ToggleButton {...propsRest} checked{value} onChange{onChange} onBlur{onBlur} ref{ref} name{name} className{componentClassName} style{{ ...componentStyle, ...style }} / )} / ) } export default ToggleButtonField注意这里Controller与RegisterOptions同样可以从redwoodjs/forms导入印证了export * from react-hook-form。模式要点Controller负责接管受控组件的 value/onChange 桥接useErrorStyles负责错误时的样式切换两者组合让第三方组件获得与原生字段一致的错误体验。SelectFieldSelectField渲染 HTMLselect标签。可以通过multipleprop 支持多选。当multiple为true时该字段返回一个按选项列表顺序排列而非按用户选择顺序的值数组SelectField nametoppings multiple{true} optionlettuce/option optiontomato/option optionpickle/option optioncheese/option /SelectField // If the user chooses lettuce, tomato, and cheese, // the onSubmit handler receives: // // { toppings: [lettuce, tomato, cheese] }源码层面SelectField的实现在 packages/forms/src/SelectField.tsx 中它同样走useErrorStylesuseRegister管线并支持emptyAs最终渲染select id{id || name} {...rest} {...styles} {...useRegisterReturn} /。这与InputField的实现模式完全一致体现了整个redwoodjs/forms包统一接线、按需定制的设计。校验下面两个示例展示了单选与多选场景的校验要求必须选择一个选项并且不允许用户保留下拉菜单的第一个占位值SelectField nameselectSingle validation{{ required: true, validate: { matchesInitialValue: (value) { return ( value ! Please select an option || Select an Option ) }, }, }} optionPlease select an option/option optionOption 1/option optionOption 2/option /SelectField FieldError nameselectSingle style{{ color: red }} /SelectField multiple{true} nameselectMultiple validation{{ required: true, validate: { matchesInitialValue: (value) { let returnValue [true] returnValue value.map((element) { if (element Please select an option) return Select an Option }) return returnValue[0] }, }, }} optionPlease select an option/option optionOption 1/option optionOption 2/option /SelectField FieldError nameselectMultiple style{{ color: red }} /这两个示例展示了 RHFvalidate选项的两种形态返回布尔值或返回字符串字符串即错误消息。多选场景中value是数组因此用map逐个检查并取returnValue[0]作为首个错误消息。强制转换通常SelectField返回字符串但你可以用valueAs系列属性返回其他类型。典型场景是用SelectField选择数字标识符不加valueAsNumber时返回字符串加上后则返回IntSelectField nameselect validation{{ valueAsNumber: true }} option value{1}Option 1/option option value{2}Option 2/option option value{3}Option 3/option /SelectField若选中Option 3Form的onSubmit函数收到的数据为{ select: 3, }值得一提的是这与空值处理一节中设置了 valueAsNumber 的 SelectFields 空值返回 NaN的规则相互呼应——两种行为都由setCoercion注入的setValueAs统一接管。FieldError当name相同的字段存在校验错误时FieldError渲染一个包含校验错误消息的span否则什么都不渲染。FieldError namename classNameerror-message !-- Renders: span classerror-messagename is required/span --它在整个表单体系中扮演就地错误提示的角色配合useErrorStyles注册进表单的服务端错误type: serverFieldError也能一并展示服务端校验消息实现客户端校验 服务端校验错误提示的统一出口。深入错误样式背后的机制把本文提到的各个组件串起来看redwoodjs/forms的整套机制可以概括为一条闭环Form内部创建useForm实例并通过FormProvider下发同时用ServerErrorsContext.Provider下发服务端字段错误映射来自error.graphQLErrors[0].extensions.properties.messages见 Form.tsx 与 ServerErrorsContext.tsx每个字段组件以InputField、CheckboxField、SelectField、TextAreaField为代表通过useRegister完成注册 类型转换注入通过useErrorStyles完成错误样式切换 服务端错误回填useErrorStyles发现服务端错误时调用setError把它写回 RHF 的formState.errors于是validationError判定成立errorClassName/errorStyle生效同时FieldError也能读取到该消息并渲染FormError则负责在表单顶部聚合展示整个提交失败的错误列表标题 明细。这一设计意味着只要遵循name一致的约定客户端校验错误、服务端校验错误、样式切换、就地提示是自动联动的一整套体系开发者几乎不需要手写任何错误状态管理代码。在测试中验证行为如果你希望以测试驱动的方式验证上述行为仓库在 packages/forms/src/tests/form.test.tsx 提供了基于 Vitest 与 Testing Library 的完整测试套件覆盖了校验、空值处理、valueAs转换、服务端错误样式等核心场景可作为自定义字段行为或表单边界条件的参考蓝本。运行该包测试的命令为yarn workspace redwoodjs/forms test该包根目录位于 packages/formspackage.json中test脚本为vitest run。小结Redwood Forms 的价值在于把 React Hook Form 的底层能力提炼成一套约定优于配置的声明式 API命名输入字段免去重复书写type与转换逻辑validationerrorClassName/errorStyle让校验与样式零散落valueAsBoolean/valueAsJSON与emptyAs使表单数据天然贴合 GraphQL 类型系统useRegister/useErrorStyles与Controller则保证了自定义组件与第三方组件库的无缝接入。掌握了这些组件与背后的setCoercion决策链你就能在 Redwood 中高效构建出类型安全、错误提示完备、可扩展的表单。赞分享后端前端Web框架开发工具【免费下载链接】redwoodRedwoodGraphQL项目地址https://gitcode.com/gh_mirrors/re/redwood点击查看免费下载相关推荐Redwood Forms 完全指南基于 React Hook Form 的表单构建与校验体系Redwood Forms 完全指南基于 React Hook Form 的表单构建与校验体系 Redwood 框架通过 redwoodjs/forms 包后端前端Web框架开发工具Redwood Forms 表单开发指南基于 React Hook Form 的表单组件体系与数据转换机制Redwood Forms 表单开发指南基于 React Hook Form 的表单组件体系与数据转换机制 导读 在 Redwood 全栈框架中 redw后端前端Web框架开发工具Redwood 表单体系完全指南基于 redwoodjs/forms 与 React Hook Form 的验证、错误处理与类型转换实战Redwood 表单体系完全指南基于 redwoodjs/forms 与 React Hook Form 的验证、错误处理与类型转换实战 导读 redwo后端前端Web框架开发工具上一篇终极指南如何实现Dio大文件断点续传下载下一篇163MusicLyrics完全免费的歌词下载神器一站式解决音乐歌词获取难题创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表