
Refine v5 中基于 React Hook Form 的 headless 表单实战useForm 适配器用法与源码解析【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refineRefine 通过refinedev/react-hook-form适配器将 React Hook Form 的全部能力以 headless 方式接入 Refine 的数据提供器、导航与变更提醒机制。本文以官方示例 form-react-hook-form-use-form 为主体讲解useForm的初始化、注册字段、校验、提交、编辑回填、文件上传与自动保存等完整流程并结合 useForm 实现源码 与 单元测试 剖析其底层工作方式。一、示例定位与文档背景documentation/docs/examples/form/react-hook-form/useForm.md是 Refine 文档中「Examples → Form → React Hook Form → useForm」的入口页。它明确指出Refine 允许你在项目中通过refinedev/react-hook-form使用 React Hook Form 库的全部特性并以此构建自己的headless表单——即不带任何 UI 库约束、完全由你自己编写 JSX 的表单。文档同时提供了 live example 与源码链接并指向 包列表文档。对应的完整可运行示例位于 examples/form-react-hook-form-use-form其中src/pages/posts/下包含create.tsx创建页、edit.tsx编辑页与list.tsx列表页三个典型场景覆盖了一个内部工具最常见的 CRUD 表单需求。二、快速上手在本地运行示例2.1 依赖一览该示例的package.json见 examples/form-react-hook-form-use-form/package.json展示了 headless 表单的最小依赖组合refinedev/coreRefine 核心提供useForm的底层数据逻辑useFormCore、useSelect、useTable、useApiUrl、useBack、useNavigation等refinedev/react-hook-form核心适配器本篇文章的主角refinedev/simple-restREST 风格数据提供器refinedev/react-router路由集成react-hook-form经由适配器间接使用适配器内部直接import { useForm as useHookForm } from react-hook-formaxios示例中用于文件上传的 HTTP 客户端。运行脚本为标准 Refine 应用三件套devrefine dev、buildtsc refine build、startrefine startNode 版本要求20。2.2 两种启动方式方式一使用 create-refine-app 拉取示例npm create refine-applatest -- --example form-react-hook-form-use-form方式二直接在仓库内运行cd examples/form-react-hook-form-use-form npm install npm run dev示例默认通过refinedev/simple-rest连接 mock REST API数据资源为posts与categories无需额外配置后端即可体验完整流程。三、创建页create.tsx从零构建 headless 表单创建页的完整代码位于 examples/form-react-hook-form-use-form/src/pages/posts/create.tsx。其核心骨架如下import { useForm } from refinedev/react-hook-form; import { useSelect, useApiUrl, useBack } from refinedev/core; export const PostCreate: React.FC () { const { refineCore: { onFinish, formLoading }, register, handleSubmit, formState: { errors }, setValue, } useForm(); // ... return ( form onSubmit{handleSubmit(onFinish)} {/* 字段注册与校验 */} /form ); };3.1 关键返回值逐项说明useForm返回的是 React Hook Form 的UseFormReturn与 Refine 核心useForm返回值的联合增强类型源码见 packages/react-hook-form/src/useForm/index.ts除了register、handleSubmit、formState、setValue等 RHF 原生命令之外还额外暴露返回值来源用途refineCore.onFinishuseFormCore提交时调用数据提供器的create/update方法refineCore.formLoadinguseFormCore提交过程中的加载态用于禁用按钮/展示 LoadingrefineCore.queryuseFormCore编辑场景下的数据查询结果saveButtonProps适配器合成{ disabled, onClick }一键绑定保存按钮3.2 注册字段与校验示例使用原生input/select/textarea配合register完成注册与校验规则声明input idtitle {...register(title, { required: true })} / {errors.title span idtitle-errorThis field is required/span} select idcategory defaultValue{} {...register(category.id, { required: true })} option value{} disabledPlease select/option {options?.map((category) ( option key{category.value} value{category.value}{category.label}/option ))} /select {errors.category span idcategory-errorThis field is required/span} textarea idcontent {...register(content, { required: true })} rows{10} cols{50} / {errors.content span idcontent-errorThis field is required/span}这里值得注意的细节required: true是 React Hook Form 内置校验规则校验失败时对应errors.field会有值可直接用于条件渲染错误提示嵌套字段category.id展示了 RHF 的点路径dot path能力。表单提交值将产生{ category: { id: 2 } }这样的嵌套结构与后端数据结构天然对齐分类下拉的数据来自useSelect({ resource: categories, pagination: { mode: server } })这是 Refine 的 headless 选择器options形如{ value, label }[]。3.3 提交流程handleSubmit 与 onFinish 的协作form onSubmit{handleSubmit(onFinish)}handleSubmit来自 React Hook Form先运行全部校验规则通过后调用传入的回调。这里的回调直接传 Refine 的onFinish因此RHF 校验所有register字段校验通过后RHF 将表单值作为参数调用onFinish(values)onFinish内部根据当前是创建还是编辑由路由 action 决定调用数据提供器的create或update。从源码看适配器对handleSubmit做了包装index.ts在真正提交前调用setWarnWhen(false)清除未保存变更标记然后转发给 RHF 原生的handleSubmit。3.4 取消与加载态const back useBack(); // ... button onClick{back}Cancel/button input typesubmit disabled{isUploading} valueSubmit / {formLoading pLoading/p}useBack是 Refine 的路由 hook返回上一页formLoading在提交期间为true可据此渲染 Loading 提示。四、编辑页edit.tsx数据回填、懒加载选择器与缩略图编辑页代码见 examples/form-react-hook-form-use-form/src/pages/posts/edit.tsx。相比创建页它多出三块核心逻辑。4.1 自动数据回填const { refineCore: { onFinish, formLoading, query: queryResult }, register, handleSubmit, formState: { errors }, setValue, saveButtonProps, } useForm();refineCore.query是编辑场景下 Refine 自动发起的数据查询按路由参数中的 id 读取记录。适配器在查询返回后会把数据自动回填到已注册字段这正是 headless 场景下「编辑页少写大量setValue」的关键。4.2 分类下拉依赖查询结果的懒加载const { options } useSelect({ resource: categories, defaultValue: queryResult?.data?.data?.category?.id, queryOptions: { enabled: !!queryResult?.data?.data?.category?.id, }, pagination: { mode: server }, });defaultValue让分类选项在数据到达后默认选中当前记录的分类queryOptions.enabled控制 categories 查询直到拿到当前记录的 category.id 才发起避免无意义的提前请求另外用useEffectsetValue(category.id, ...)兜底同步确保 select 组件的受控值在数据到达后正确写入表单edit.tsx。4.3 缩略图展示与saveButtonProps{queryResult?.data?.data?.thumbnail ( img src{queryResult?.data?.data?.thumbnail} width{200} height{200} / )} input typesubmit valueSubmit disabled{saveButtonProps.disabled} /saveButtonProps是适配器额外提供的便捷属性disabled在formLoading时自动为true源码 index.tsonClick内部等价于触发handleSubmit(values onFinish(values).catch(() {}))。也就是说你可以直接把它展开到button {...saveButtonProps}上无需手写提交逻辑。五、文件上传axios 上传后 setValue 回填创建页中上传图片的部分展示了 headless 表单「外部异步数据回填表单」的典型写法const apiURL useApiUrl(); const onSubmitFile async () { setIsUploading(true); const inputFile document.getElementById(fileInput) as HTMLInputElement; const formData new FormData(); formData.append(file, inputFile?.files?.item(0) as File); const res await axios.post{ url: string }( ${apiURL}/media/upload, formData, { withCredentials: false, headers: { Access-Control-Allow-Origin: * } }, ); setValue(thumbnail, res.data.url); setIsUploading(false); };要点useApiUrl()从 Refine 上下文取出数据提供器的 API 地址上传成功后用 RHF 的setValue(thumbnail, res.data.url)把返回的 URL 写入隐藏字段input idfileInput typefile onChange{onSubmitFile} / input typehidden {...register(thumbnail)} /isUploading控制提交按钮的disabled防止上传未完成就提交。这是「先异步处理、再回填表单」的标准模式编辑页中则直接展示已存在的thumbnail图片形成创建/编辑闭环。六、列表页与接口定义6.1 列表页list.tsx 使用useTable读取数据并渲染表格配合useNavigation的create/edit跳转const { tableQuery: tableQueryResult } useTableIPost({ sorters: { initial: [{ field: id, order: desc }] }, }); const { edit, create } useNavigation(); // button onClick{() create(posts)}Create Post/button // button onClick{() edit(posts, post.id)}Edit/button6.2 接口类型interfaces/index.d.ts 定义了文章与分类的结构其中的status联合类型与创建/编辑页的select选项一一对应export interface ICategory { id: number; title: string; } export interface IPost { id: number; title: string; content: string; status: published | draft | rejected; }七、源码深度解析useForm 适配器如何工作理解了示例用法后阅读 packages/react-hook-form/src/useForm/index.ts 可以看清适配器的完整实现共四层职责。7.1 双 useForm 组合RHF 管表单Core 管数据适配器内部同时调用两个useFormconst useHookFormResult useHookFormTVariables, TContext({ ...rest }); const useFormCoreResult useFormCore...({ ...refineCoreProps, onMutationError });useHookForm来自 react-hook-form负责字段注册、校验、状态管理useFormCore来自refinedev/core负责数据获取query、提交onFinish、加载态formLoading与自动保存onFinishAutoSave两者的配置通过refineCoreProps与剩余参数...rest分别透传互不干扰。返回类型UseFormReturnType正是二者返回值的交叉增强index.ts。7.2 配置项与默认值UseFormProps在 React Hook Form 原生UseHookFormProps基础上扩展了三个 Refine 专属配置index.ts配置项默认值作用refineCorePropsundefined透传给useFormCore的配置resource、action、autoSave等warnWhenUnsavedChanges继承 Refine 全局配置开启后表单值变化且未提交时离开页面会弹出确认框disableServerSideValidationfalse设为true可关闭服务端校验错误到表单字段的错误映射其中warnWhenUnsavedChanges的生效路径是适配器监听watch任一字段值变化时调用setWarnWhen(true)index.ts而handleSubmit提交成功前会setWarnWhen(false)清除标记。这解释了「未保存变更提示」这一完整闭环。7.3 服务端校验错误自动映射disableServerSideValidation提交失败时Refine 数据提供器返回的HttpError可能带errors对象。适配器在useFormCore的onMutationError钩子中把服务端字段错误自动映射到对应表单字段index.ts错误值为数组如[Title is required]→ 用空格 join 成字符串错误值为字符串 → 直接使用错误值为true→ 使用兜底文案Field is not valid.错误值为{ key, message }对象 → 调用translate(key, message)做 i18n 翻译后展示只有已注册到表单的字段通过flattenObjectKeys(_variables)对比才会映射未注册字段会被跳过。单元测试 packages/react-hook-form/src/useForm/index.spec.tsx 构造了一个包含字符串、数组、true、{key,message}四种错误形态的HttpError逐一验证映射逻辑同时验证i18nProvider的translate参与翻译form.error.content被译为Translated content error。7.4 查询数据回填不覆盖用户已编辑内容编辑页自动回填的核心实现是「延迟同步 脏值保护」查询数据到达后通过queueMicrotask降级为Promise.resolve().then把同步延迟到字段注册 effect 之后保证register已挂载的字段能拿到数据index.ts用syncedFieldsRef记录已同步字段避免重复setValue覆盖用户输入对后期才挂载的字段如Controller组件适配器每轮渲染检查control._names.mount集合发现新字段才做一次尊重脏状态respectDirty的同步即用户已修改的字段不再覆盖index.ts。这套机制保证了「数据加载 → 回填 → 用户编辑 → 再次数据更新」各阶段都不会丢失用户输入。7.5 自动保存autoSave若refineCoreProps.autoSave?.enabled为true则onValuesChange会走自动保存分支调用onFinishAutoSave并可通过autoSave.onFinish自定义提交前的数据转换index.ts。这意味着 useForm 适配器天然支持「输入即保存」的体验无需额外接入轮询或手动定时器。八、可复用清单何时选择 useForm 适配器结合文档定位与示例实践refinedev/react-hook-form的useForm适合以下场景项目希望完全掌控 UIheadless不愿被 antd、MUI 等组件库的表单封装约束团队已熟悉 React Hook Form 的register/handleSubmit/formState心智模型需要 Refine 的数据层能力CRUD 提交、数据回填、自动保存、未保存变更提示、服务端错误映射又希望复用 RHF 强大的校验生态zod、yup等 schema 校验可经 RHF 的resolver接入。若需要 Modal 表单或分步表单同一包还提供了 useModalForm 与 useStepsForm 两个姊妹 hook均围绕同一套「RHF 表单层 Refine 数据层」的组合模式实现可作为后续深入的方向。九、总结本文以documentation/docs/examples/form/react-hook-form/useForm.md为入口围绕 form-react-hook-form-use-form 示例完整走通了 headless 表单的创建、编辑、校验、提交、文件上传与列表跳转流程并深入 useForm 源码 解析了双 useForm 组合、服务端错误映射、自动回填、未保存变更提示与自动保存的实现原理最后通过 单元测试 验证了核心行为。掌握这套组合你就可以在完全自主控制 UI 的前提下获得 Refine 数据层与 React Hook Form 校验层的双重能力。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考