ARTICLE DETAIL

资讯详情

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

Refine 与 React Hook Form 集成指南:用 @refinedev/react-hook-form 构建 Headless 表单

Refine 与 React Hook Form 集成指南:用 @refinedev/react-hook-form 构建 Headless 表单 Refine 与 React Hook Form 集成指南用 refinedev/react-hook-form 构建 Headless 表单【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine导读本文基于 Refine v5 官方文档与refinedev/react-hook-form包源码系统讲解如何在 Refine 项目中以 headless 方式接入 React Hook Form实现表单校验、数据回填、提交与自动保存。读完本文你将掌握useForm、useModalForm、useStepsForm三个核心 Hook 的用法理解它们与 Refine 数据提供器Data Provider之间的底层协作机制并能将其无缝用于 Ant Design、Material UI、Chakra UI 等任意 UI 库。包概览一个为 Refine 而生的 React Hook Form 适配层Refine 为 React Hook Form 提供了官方集成包refinedev/react-hook-form。该包的核心设计理念是headless它不绑定任何 UI 组件库而是把表单状态、校验、数据获取与提交逻辑全部托管给 Hook由开发者自由选择渲染层。这个适配包同时支持了 React Hook Form 与 Refine 的 useForm 的全部能力。文档中明确指出你可以把任意一个 React Hook Form 官方示例原样复制粘贴进 Refine 项目直接使用因为适配层保留了对 React Hook Form 原版useForm的全部透传能力。从包的入口文件 packages/react-hook-form/src/index.ts 可以看到该包导出以下三个表单管理 HookHook用途useForm基础表单管理适用于普通页面表单useModalForm在 Modal 或 Drawer 中展示表单可处理路由参数缺失的场景useStepsForm分步表单Step Form适合多步骤流程录入其中useForm是基础另外两个 Hook 在其之上扩展。例如 useModalForm 的源码 直接复用了useForm的返回类型并额外返回modal对象submit、close、show、visible、title以及defaultVisible、autoSubmitClose、autoResetForm、autoResetFormWhenClose等 Modal 配置。安装一行命令接入在 Refine v5 项目中安装集成包npm install refinedev/react-hook-form从仓库中的 packages/react-hook-form/package.json 可以看到真实的依赖约束运行时依赖react-hook-form^7.57.0、lodashPeer 依赖refinedev/core^5.0.0、react^18 || ^19、react-dom^18 || ^19Node 版本要求20。也就是说你的项目需要同时安装refinedev/core5与 React Hook Form v7 系列适配包才能正常工作。快速上手编辑文章表单Headless 示例下面这段代码来自官方文档展示用useForm在 Headless 场景下编辑一篇博客文章。它不依赖任何 UI 库只用原生form、input、select、textareaimport { HttpError } from refinedev/core; import { useForm } from refinedev/react-hook-form; export const PostEdit () { const { refineCore: { onFinish, formLoading, query }, register, handleSubmit, formState: { errors }, } useFormIPost, HttpError({ refineCoreProps: { resource: posts, action: edit, id: 1, }, }); return ( form onSubmit{handleSubmit(onFinish)} labelTitle: /label input {...register(title, { required: true })} / {errors.title spanThis field is required/span} br / labelStatus: /label select {...register(status)} option valuepublishedpublished/option option valuedraftdraft/option option valuerejectedrejected/option /select br / labelContent: /label textarea {...register(content, { required: true })} rows{10} cols{50} / {errors.content spanThis field is required/span} br / input typesubmit valueSubmit / {formLoading pLoading/p} /form ); }; export type IStatus published | draft | rejected; interface IPost { id: number; title: string; content: string; status: IStatus; }这段代码中有几个关键设计值得注意refineCoreProps是 Refine 核心配置的命名空间resource指定数据资源action: edit告诉 Refine 这是一次编辑操作id指定要编辑的记录。这些参数会被透传给 Refine 核心的useFormregister与formState.errors来自 React Hook Form校验规则如required: true完全遵循 React Hook Form 的 API 约定refineCore.onFinish与formLoading来自 RefinehandleSubmit(onFinish)表示表单校验通过后把数据交给 Refine 的变更流程formLoading在编辑模式抓取数据或提交期间为true。数据回填是怎么发生的在action: edit模式下Refine 会用id调用数据提供器的getOne方法拉取记录并通过query暴露给表单。适配层在 useForm 源码 中实现了“查询结果到表单字段的同步”逻辑当query数据到达后通过setValue将数据按字段路径写入表单同时用syncedFieldsRef记录已同步字段避免覆盖用户已经编辑过的 dirty 值对于Controller这类延迟注册的字段还会在字段挂载后再补一次同步。仓库中的测试用例 packages/react-hook-form/src/useForm/index.spec.tsx 专门覆盖了“query 解析后延迟注册字段的值同步”场景。与 UI 库结合Material UI 与 Chakra UI 示例headless 并不意味着放弃 UI 库。适配包把表单状态交给 HookUI 层则可以自由选用任何组件库。Material UI 版本使用refinedev/mui的Edit组件包裹表单配合 MUI 的TextField和Autocompleteimport { HttpError } from refinedev/core; import { Edit } from refinedev/mui; import Box from mui/material/Box; import TextField from mui/material/TextField; import Autocomplete from mui/material/Autocomplete; import { useForm } from refinedev/react-hook-form; import { Controller } from react-hook-form; export const PostEdit: React.FC () { const { saveButtonProps, register, control, formState: { errors }, } useFormIPost, HttpError({ refineCoreProps: { resource: posts, action: edit, id: 1, }, }); return ( Edit saveButtonProps{saveButtonProps} Box componentform sx{{ display: flex, flexDirection: column }} autoCompleteoff TextField idtitle {...register(title, { required: This field is required, })} error{!!errors.title} helperText{errors.title?.message} marginnormal fullWidth labelTitle nametitle autoFocus / Controller control{control} namestatus rules{{ required: This field is required }} // eslint-disable-next-line defaultValue{null as any} render{({ field }) ( AutocompleteIStatus idstatus options{[published, draft, rejected]} {...field} onChange{(_, value) { field.onChange(value); }} renderInput{(params) ( TextField {...params} labelStatus marginnormal variantoutlined error{!!errors.status} helperText{errors.status?.message} required / )} / )} / TextField idcontent {...register(content, { required: This field is required, })} error{!!errors.content} helperText{errors.content?.message} marginnormal labelContent multiline rows{4} / /Box /Edit ); }; export type IStatus published | draft | rejected; export interface IPost { id: number; title: string; content: string; status: IStatus; }注意这里的几个新要素saveButtonProps由适配层生成的提交按钮属性disabled、onClick直接传给Edit组件即可获得“提交中禁用按钮 触发提交”的完整行为。从源码看saveButtonProps.onClick内部会调用handleSubmit((v) onFinish(v))Controller用于非原生控件MUI 的Autocomplete不是受控原生表单元素必须通过react-hook-form的Controller桥接control正是从useForm解构出来的校验信息接入 UIerror与helperText直接消费formState.errors。Chakra UI 版本Chakra UI 的写法与 Material UI 类似只是换用refinedev/chakra-ui的Edit与 Chakra 的FormControl、FormLabel、FormErrorMessage等组件import { HttpError } from refinedev/core; import { useForm } from refinedev/react-hook-form; import { Edit } from refinedev/chakra-ui; import { FormControl, FormErrorMessage, FormLabel, Input, Select, Textarea, } from chakra-ui/react; export const PostEdit () { const { refineCore: { formLoading }, saveButtonProps, register, formState: { errors }, } useFormIPost, HttpError({ refineCoreProps: { resource: posts, action: edit, id: 1, }, }); return ( Edit isLoading{formLoading} saveButtonProps{saveButtonProps} FormControl mb3 isInvalid{!!errors?.title} FormLabelTitle/FormLabel Input idtitle typetext {...register(title, { required: Title is required })} / FormErrorMessage{${errors.title?.message}}/FormErrorMessage /FormControl FormControl mb3 isInvalid{!!errors?.status} FormLabelStatus/FormLabel Select idstatus placeholderSelect Post Status {...register(status, { required: Status is required, })} optionpublished/option optiondraft/option optionrejected/option /Select FormErrorMessage{${errors.status?.message}}/FormErrorMessage /FormControl FormControl mb3 isInvalid{!!errors?.content} FormLabelContent/FormLabel Textarea idcontent {...register(content, { required: content is required, })} / FormErrorMessage{${errors.content?.message}}/FormErrorMessage /FormControl /Edit ); }; export type IStatus published | draft | rejected; interface IPost { id: number; title: string; content: string; status: IStatus; }可以看出三种形态Headless / MUI / Chakra共享同一套useForm调用方式差异只存在于渲染层。这正是 headless 适配的价值表单业务逻辑与 UI 完全解耦切换 UI 库时无需重写数据逻辑。深入 useForm与 Refine 核心 Hook 的协作原理useForm是适配包的核心。其源码 packages/react-hook-form/src/useForm/index.ts 清晰地展示了它的组装方式const useHookFormResult useHookFormTVariables, TContext({ ...rest }); const useFormCoreResult useFormCoreTQueryFnData, TError, TVariables, TData, TResponse, TResponseError({ ...refineCoreProps, // ... });即内部同时调用 React Hook Form 的useForm与 Refine 核心的useForm来自refinedev/core把两者的返回值合并后返回。适配层额外做了三件关键工作合并返回值最终返回对象由...useHookFormResultReact Hook Form 的全部能力refineCoreRefine 核心返回值saveButtonProps提交按钮属性组成查询数据同步到表单如上文所述将useOne查询结果回填到已注册字段且不覆盖用户编辑服务端校验错误映射在onMutationError回调中把数据提供器返回的字段级错误error.errors通过setError映射到 React Hook Form 的字段错误上从而支持服务端校验。若设置了disableServerSideValidation则会跳过这一映射。未保存更改提醒源码中通过useWarnAboutChange与warnWhenUnsavedChanges实现“离开页面时的未保存更改提醒”默认继承 Refine 全局配置也可通过 Hook 的warnWhenUnsavedChanges属性单独覆盖用户每次触发handleSubmit或启用autoSave时都会重置警告状态。refineCoreProps 属性详解useForm的配置通过refineCoreProps命名空间透传给 Refine 核心 Hook。以下是官方文档列出的核心属性详见 use-form 文档属性说明action表单动作create/edit/clone。默认从当前路由推断匹配资源动作路径Modal 或自定义路由场景需显式传入resource资源名作为数据提供器方法的参数通常是 API 端点路径默认从当前 URL 读取id编辑/克隆的记录标识默认取自路由可用setId动态修改。edit与clone模式必填redirect提交成功后的跳转目标默认list可设为show \| edit \| list \| create或false禁止跳转onMutationSuccess变更成功回调接收data、variables、context、isAutoSave四个参数onMutationError变更失败回调参数同上invalidates控制变更结束时的查询失效范围。create/clone默认失效[list, many]edit默认失效[list, many, detail]dataProviderName多数据提供器场景下指定使用的数据提供器mutationMode变更模式pessimistic默认/optimistic/undoablesuccessNotification/errorNotification自定义成功/失败通知需配置NotificationProvidermeta/queryMeta/mutationMeta传给数据提供器的附加信息。queryMeta只作用于useOne查询mutationMeta只作用于变更二者优先级高于metaqueryOptionsedit/clone模式下useOne的查询选项如retrycreateMutationOptions/updateMutationOptionscreate/clone与edit模式下的变更选项warnWhenUnsavedChanges未保存更改提醒默认false可在Refine组件全局配置liveMode/onLiveEvent/liveParams实时Realtime订阅相关配置liveMode: auto时收到事件自动更新数据autoSave自动保存配置详见下文多个同名资源使用identifier当项目存在多个同名资源时可通过identifier指定唯一匹配键。数据提供器的方法仍以Refine组件中资源的name为准。自定义资源时注意 id 的来源文档特别提醒显式传入resource后URL 中的id会被忽略因为该 id 可能属于另一个资源。此时需配合useParsed从 URL 解析 id 并传入或用setId手动设置import { useParsed } from refinedev/core; import { useForm } from refinedev/react-hook-form; const { id } useParsed(); useForm({ refineCoreProps: { resource: custom-resource, id, }, });三种 action 模式的底层行为useForm会根据action决定底层调用哪些数据提供器方法create调用数据提供器的create方法创建新记录底层对应 Refine 的useCreateedit先用useOnegetOne按 id 拉取数据回填表单提交后调用update底层对应useUpdateclone相当于“另存为”。先用useOne拉取数据回填提交后调用create创建一条新记录底层对应useCreate。从返回值的角度看refineCore.query保存的是useOne的查询结果refineCore.mutation保存的是useCreate/useUpdate的变更结果。自动保存autoSaveautoSave让表单在用户编辑后自动保存适合“边编辑边保存”的体验。它仅作用于edit模式创建新数据时仍需手动提交。相关配置项如下配置项说明enabled是否开启默认关闭debounce防抖时间毫秒默认1000onFinish保存前修改数据的回调可对值做二次加工invalidateOnUnmount组件卸载时是否失效查询默认false关闭后自动保存默认不失效任何查询useForm({ refineCoreProps: { autoSave: { enabled: true, debounce: 2000, onFinish: (values) { return { foo: bar, ...values, }; }, invalidateOnUnmount: true, }, }, });开启后Hook 会额外返回autoSaveProps包含data、error、status三个属性可用于展示自动保存状态。此外onMutationSuccess/onMutationError回调中的isAutoSave参数可用来区分触发来源是否为自动保存。返回值速查useForm的返回值为React Hook FormuseForm的全部返回值 Refine 核心返回值挂载在refineCore下 两个扩展属性返回值说明refineCore.queryedit/clone模式下useOne查询结果query.data即记录数据refineCore.mutationcreate/clone对应useCreateedit对应useUpdate的变更结果refineCore.onFinish表单提交入口根据action自动调用相应变更refineCore.formLoading提交中或编辑/克隆数据抓取中的加载状态refineCore.setId动态修改当前编辑/克隆的记录 idrefineCore.redirect编程式跳转函数如redirect(show, data?.data?.id)saveButtonProps提交按钮属性{ disabled: boolean; onClick: (e) void }autoSaveProps自动保存状态{ data, error, status }常见问题FAQ如何失效其他资源用useInvalidate可以失效与当前资源无关的其他资源查询。例如编辑文章成功后同时刷新users资源import { useInvalidate } from refinedev/core; import { useForm } from refinedev/react-hook-form; const PostEdit () { const invalidate useInvalidate(); useForm({ refineCoreProps: { onMutationSuccess: (data, variables, context) { invalidate({ resource: users, invalidates: [resourceAll], }); }, }, }); };如何在提交前修改表单数据用handleSubmit包一层自定义处理器在调用refineCore.onFinish前重组数据。例如把name与surname合并为fullName再提交import { useForm } from refinedev/react-hook-form; import { FieldValues } from react-hook-form; export const UserCreate: React.FC () { const { refineCore: { onFinish }, register, handleSubmit, } useForm(); const onFinishHandler (data: FieldValues) { onFinish({ fullName: ${data.name} ${data.surname}, }); }; return ( form onSubmit{handleSubmit(onFinishHandler)} labelName: /label input {...register(name)} / br / labelSurname: /label input {...register(surname)} / br / button typesubmitSubmit/button /form ); };如何分别给查询与变更传不同的 meta当查询useOne与变更useCreate/useUpdate需要不同附加参数时分别使用queryMeta与mutationMeta两者与meta属性重叠时前者优先生效。类型参数与外部属性useForm是泛型 Hook官方文档给出的类型参数及其默认值如下类型参数说明默认值TQueryFnData查询函数返回的数据类型需继承BaseRecordBaseRecordTError自定义错误类型需继承HttpErrorHttpErrorTVariables变更函数接收的字段值类型{}TContextReact Hook FormuseForm的第二个泛型{}TDataselect函数返回的数据类型TQueryFnDataTResponse变更函数返回的数据类型TDataTResponseError变更错误类型TError同时useForm还接受 React Hook FormuseForm的全部属性如mode、defaultValues、resolver等因为源码中通过...rest将这些属性原样透传给了 React Hook Form。这意味着 Zod、Yup 等 schema 校验器可以直接通过resolver接入。配套示例与测试仓库提供了可直接运行的完整示例工程 examples/form-react-hook-form-use-form其中create.tsx、edit.tsx 展示了创建与编辑页的完整写法编辑页结合useSelect加载关联分类数据并通过setValue/defaultValue处理嵌套字段category.id的回填可作为实际项目的参考模板。针对该适配包仓库还提供了单元测试 packages/react-hook-form/src/useForm/index.spec.tsx覆盖了“query 解析后延迟注册字段的值同步”“表单字段注册”等关键行为可作为理解适配层内部同步机制的补充材料。小结refinedev/react-hook-form通过一层轻量适配将 React Hook Form 的表单能力与 Refine 的数据层无缝衔接register/handleSubmit/formState负责表单交互refineCore负责数据获取与变更saveButtonProps与autoSave补齐了企业级表单的高频需求。无论你使用纯 Headless 写法还是 Ant Design、Material UI、Chakra UI、Mantine 中的任意 UI 库这套表单逻辑都可以原样复用——这正是 Refine 面向管理后台、内部工具类应用的灵活性的具体体现。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表