
Refine useForm 深度解析面向 create / edit / clone 三种场景的 Headless 表单 Hook【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine本文以 Refinerefine官方 3.x 版本文档useForm的 API Reference 为主体系统讲解这一 Headless 表单 Hook 的三种 action 模式、完整数据流、全部配置参数与返回值并结合当前仓库 packages/core/src/hooks/form/index.ts 中的最新源码实现剖析useForm在底层如何编排useOne、useCreate、useUpdate完成表单提交。读完本文你将能够独立搭建基于useForm的创建/编辑/克隆表单页面理解其重定向、缓存失效、变更模式mutation mode与通知机制并掌握自动保存autoSave等仓库中已落地的进阶用法。useForm 的定位连接状态与 dataProvider 的桥梁useForm是 Refine 中用于管理表单的 Hook它内置了处理create、edit和clone三种action的能力。Hook 的返回值与调用时对应的action相匹配并会依据不同的action执行不同的逻辑。可以把useForm理解为你的表单state与dataProvider之间的桥梁。它是一个低层级的 Hook专门用于构建你自己的表单组件同时它还会借助notificationProvider根据当前action和dataProvider的响应结果向用户反馈通知。需要注意的一点原文档明确强调useForm本身不管理任何表单状态。如果你需要的是完整的表单库Refine 官方支持三种开箱即用的表单库React Hook Form面向 Headless 用户Ant Design Form面向 Ant Design 用户Mantine Form面向 Mantine 用户。这三类库在 Refine 中同样提供了各自的useForm封装与本文讲解的 core 版本useForm是不同层次的抽象使用时不要混淆。三种 action 的底层工作流程useForm的行为围绕action展开默认行为可以分为三个分支action: create表单提交后useForm调用onFinish函数传入表单值onFinish内部调用 useCreate Hook并传入表单值useCreate调用dataProvider的create方法并返回响应根据响应状态useForm调用onSuccess或onErroronSuccess/onError进而调用notificationProvider的open方法通知用户useForm重定向到list页面。action: edit挂载时useForm先调用 useOne Hook 获取待编辑的记录其中id来自URL或 props。表单提交后useForm调用onFinish传入表单值onFinish内部调用 useUpdateuseUpdate调用dataProvider的update方法并返回响应根据响应状态调用onSuccess或onError进而调用notificationProvider的open通知用户重定向到list页面。action: clone挂载时与edit类似useForm调用useOne获取待克隆的记录id来自URL或 props。可以把它理解为另存为save as与edit相同的是先读取记录不同的是提交时创建一条新记录而非更新原记录——onFinish内部调用useCreate随后同样经过通知与重定向到list页面。以上是默认行为。你可以通过传入redirect、onMutationSuccess、onMutationError等 props 来自定义它。基础用法下面是一个最小可运行的创建表单继承自原文档 Basic Usage 示例import { useState } from react; import { useForm } from pankod/refine-core; const PostCreate () { const [title, setTitle] useState(); const { onFinish } useForm({ action: create, }); const onSubmit (e) { e.preventDefault(); onFinish({ title }); }; return ( form onSubmit{onSubmit} input onChange{(e) setTitle(e.target.value)} / button typesubmitSubmit/button /form ); };调用onFinish后会返回mutationResultuseForm接受泛型类型参数用于定义 mutation 与 query 的响应类型。完整配置参数详解PropertiesactionuseForm支持edit、create、clone三种 action。默认行为从路由推断action。路由为/posts/create时Hook 以action: create运行路由为/posts/edit/1时以action: edit运行路由为/posts/clone/1时以action: clone运行。当无法从路由推断 action 时例如表单放在弹窗中、或使用了自定义路由可以显式传入actionprop 覆盖。action: create用于创建一条此前不存在的记录底层使用useCreate执行 mutationaction: edit用于编辑已有记录需要id。默认使用路由中的id可通过setId函数或id属性修改挂载时用useOne按id拉取记录并通过queryResult返回供你填充表单提交后调用useUpdateaction: clone用于克隆已有记录。同样需要id可用setId修改用useOne拉取记录填充表单提交时通过useCreate创建新记录。resource默认值从当前 URL 读取resource。该值会作为参数传给dataProvider的各个方法通常被用作 API 端点路径具体取决于你的dataProvider如何处理resource。按 action 不同action: create时传给dataProvider的create方法action: edit时传给update和getOne方法action: clone时传给create和getOne方法。useForm({ resource: categories, });idid用于确定要edit或clone的记录。默认使用路由中的id也可以通过setId函数或id属性修改。当你要在非标准页面例如详情页编辑/克隆某个资源时非常有用。注意action: edit或action: clone时id是必需的。useForm({ action: edit, // or clone resource: categories, id: 1, // BASE_URL_FROM_DATA_PROVIDER/categories/1 });redirect用于指定表单提交成功后的重定向页面默认为list。可设置为show | edit | list | create或设为false以阻止提交后自动跳转。useForm({ redirect: false, });onMutationSuccessmutation 成功后的回调接收三个参数data根据action不同来自useCreate或useUpdate的返回数据variables传给 mutation 的变量contextreact-query 的上下文。useForm({ onMutationSuccess: (data, variables, context) { console.log({ data, variables, context }); }, });onMutationErrormutation 失败后的回调参数结构同上data、variables、contextuseForm({ onMutationError: (data, variables, context) { console.log({ data, variables, context }); }, });invalidates用于管理 mutation 结束后触发的缓存失效invalidation。针对当前resource的默认失效范围是create或clone模式失效list和manyedit模式失效list、many和detail。useForm({ invalidates: [list, many, detail], });dataProviderName当你注册了多个dataProvider时需要指明当前表单使用哪一个。它适合为特定资源切换dataProvider。提示如果希望在所有资源页面统一使用不同的dataProvider可以直接使用Refine组件的dataProvider属性无需在useForm里逐个指定。useForm({ dataProviderName: second-data-provider, });mutationMode变更模式决定 mutation 以何种方式执行共有三种pessimistic、optimistic和undoable默认是pessimistic。每种模式对应不同的用户体验pessimistic先请求、成功后更新 UI传统做法optimistic先更新 UI请求失败再回滚undoable先更新 UI在超时窗口内提供撤销入口。useForm({ mutationMode: undoable, // pessimistic | optimistic | undoable });successNotification前提需要配置NotificationProvider才生效。表单提交成功后useForm会调用NotificationProvider的open方法展示成功通知。该属性用于自定义成功通知的内容useForm({ successNotification: (data, values, resource) { return { message: Post Successfully created with ${data.title}, description: Success with no errors, type: success, }; }, });errorNotification同样依赖NotificationProvider。表单提交失败后useForm调用open展示错误通知可用该属性自定义。不传时的默认值如下{ message: Error when updating resource-name (status code: ${err.statusCode}) 或 Error when creating resource-name (status code: ${err.statusCode}), description: Error, type: error }useForm({ errorNotification: (data, values, resource) { return { message: Something went wrong when deleting ${data.id}, description: Error, type: error, }; }, });metaDatametaData有两个用途向 data provider 方法传递额外信息使用普通 JavaScript 对象JSON生成 GraphQL 查询用于 GraphQL dataProvider 场景。例如把headers放进metaData传给create方法类似的逻辑可以让你向 data provider 传递任意自定义属性useForm({ metaData: { headers: { x-meta-data: true }, }, }); const myDataProvider { //... create: async ({ resource, variables, metaData }) { const headers metaData?.headers ?? {}; const url ${apiUrl}/${resource}; const { data } await httpClient.post(url, variables, { headers }); return { data, }; }, //... };queryOptions仅在action: edit或action: clone模式下生效。edit/clone模式下 Refine 使用useOne拉取数据可以通过queryOptions传入 react-query 的useQuery选项useForm({ queryOptions: { retry: 3, }, });createMutationOptions仅在action: create或action: clone时可用。create/clone模式下 Refine 用useCreate创建数据可通过createMutationOptions传入 react-query 的useMutation选项useForm({ createMutationOptions: { retry: 3, }, });updateMutationOptions仅在action: edit时可用。edit模式下 Refine 用useUpdate更新数据可通过updateMutationOptions传入useMutation选项useForm({ updateMutationOptions: { retry: 3, }, });liveMode / onLiveEvent / liveParams实时Live / Realtime相关配置配合liveProvider使用liveMode接收到相关 live 事件时是否自动更新数据auto或不自动更新manual用于在全应用范围内实时更新并展示数据onLiveEvent订阅到达新事件时执行的回调liveParams传给liveProvider的subscribe方法的参数。useForm({ liveMode: auto, onLiveEvent: (event) { console.log(event); }, });返回值Return Values属性说明类型onFinish触发 mutation 的函数(values: TVariables) PromiseCreateResponseTData \| UpdateResponseTData \| voidqueryResult记录查询query的结果QueryObserverResultTmutationResult调用onFinish后触发的 mutation 结果UseMutationResultTformLoading表单请求的加载状态booleanidclone/edit场景下的记录 idBaseKeysetIdid的 setterDispatchSetStateActionstring \| number \| undefinedredirect自定义重定向函数(redirect: list\|edit\|show\|create\|false, idFromFunction?: BaseKey) voidqueryResult当action为edit或clone或者提供了带id的resource时useForm会调用useOne并把结果作为queryResult属性返回const { queryResult } useForm(); const { data } queryResult;mutationResultcreate/clone模式下useForm调用useCreateedit模式下调用useUpdate并把结果作为mutationResult返回const { mutationResult } useForm(); const { data } mutationResult;setIduseForm默认从 router 推断id。若想动态修改id可以使用setIdconst { id, setId } useForm(); const handleIdChange (id: string) { setId(id); }; return ( div input value{id} onChange{(e) handleIdChange(e.target.value)} / /div );redirect默认情况下mutation 成功后useForm会重定向到list页面。要跳到其他页面可以用返回的redirect函数在代码中指定目标或在 hook 选项中设置redirect属性。例如成功提交后跳转到show页面const { onFinish, redirect } useForm(); // -- const handleSubmit async (e: React.FormEventHTMLFormElement) { e.preventDefault(); const data await onFinish(formValues); redirect(show, data?.data?.id); }; // --onFinishonFinish在表单提交时被调用会根据action选择对应的 mutation。你也可以在 hook 选项中传入自定义的onFinish函数来覆盖默认行为例如在提交前修改表单数据见下文 FAQ。formLoading表单请求的加载状态当useForm正在提交或正在为edit/clone模式拉取数据时为true。适合直接绑定到提交按钮的disabled上。源码级实现剖析结合当前仓库以下分析基于当前仓库中 useForm 的实现文件。该文件是 3.x 文档所述实现的直接演进refinedev/corev5核心编排逻辑与文档描述一致同时扩展了 autoSave、overtime 等能力。参数解析从路由推断 action 与 id实现开头通过useResourceParams一次性解析出id、resource、formActionconst { id, setId, resource, identifier, formAction: action, } useResourceParams({ resource: props.resource, id: props.id, action: props.action, }); const isEdit action edit; const isClone action clone; const isCreate action create;随后基于三个布尔值决定后续所有行为分支。若edit/clone场景未定义id且未通过queryOptions.enabled: false禁用查询会用warnOnce输出开发态警告提示应显式传入idprop 或调用setId——这正对应文档中id在 edit/clone 时必需的约束。重定向策略的解析重定向目标由redirectPage辅助函数统一计算优先级是 props 中的redirect 全局默认配置来自useRefineOptions 按 action 推导的默认值const redirectAction redirectPage({ redirectFromProps: props.redirect, action, redirectOptions: defaultRedirect, });返回给用户的redirect函数内部调用handleSubmitWithRedirect来自useRedirectionAfterSubmission支持第二个参数传入目标 id、第三个参数传入 route paramsconst redirect: UseFormReturnType[redirect] ( redirect isEdit ? list : edit, redirectId id, routeParams {}, ) { handleSubmitWithRedirect({ redirect: redirect, resource, id: redirectId, meta: { ...pickedMeta, ...routeParams }, }); };查询与 mutation 的编排Hook 同时挂载了三个数据 Hook并依据action选用其一const queryResult useOneTQueryFnData, TError, TData({ resource: identifier, id, queryOptions: { ...props.queryOptions, // 只有非 create 且 id 已定义时才启用查询 enabled: !isCreate id ! undefined (props.queryOptions?.enabled ?? true), }, liveMode: props.liveMode, onLiveEvent: props.onLiveEvent, liveParams: props.liveParams, meta: { ...combinedMeta, ...props.queryMeta }, dataProviderName: props.dataProviderName, }); const createMutation useCreateTResponse, TResponseError, TVariables({ mutationOptions: props.createMutationOptions, }); const updateMutation useUpdateTResponse, TResponseError, TVariables({ mutationOptions: props.updateMutationOptions, }); const mutationResult isEdit ? updateMutation : createMutation; const isMutationLoading mutationResult.mutation.isPending; const formLoading isMutationLoading || queryResult.query.isFetching;这里可以看到文档中queryOptions 仅在 edit/clone 生效的源码依据useOne的enabled被强制与!isCreate id ! undefined做与运算即使你显式传了enabledcreate 模式下查询也不会发起。formLoading则是 mutation 的 pending 状态与查询的 isFetching 状态的并集即提交中或拉取编辑/克隆数据中。onFinish一次提交如何走完全流程onFinish是整个 Hook 的核心源码约 L214–L303。它返回一个 Promise内部流程完整对应文档描述的工作流前置校验缺少必要参数时直接 reject并带有明确的错误文案// Reject the mutation if the resource is not defined if (!resource) return reject(missingResourceError); // Reject the mutation if the id is not defined in clone action if (isClone !id) return reject(missingIdError); // Reject the mutation if theres no values passed if (!values) return reject(missingValuesError); // Auto Save is only allowed in edit action if (isAutosave !isEdit) return reject(autosaveOnNonEditError);按 mutationMode 决定重定向时机非悲观模式optimistic/undoable下重定向被deferExecution延迟执行先提交 UI 状态再跳转Promise 立即 resolve悲观模式则在 mutation 成功后、.then分支中延迟重定向到目标页面mutateAsync(variables as any, { onSuccess: props.onMutationSuccess ? (data, _, context) { props.onMutationSuccess?.(data, values, context, isAutosave); } : undefined, onError: props.onMutationError ? (error: TResponseError, _, context) { props.onMutationError?.(error, values, context, isAutosave); } : undefined, }) .then((data) { if (isPessimistic !isAutosave) { deferExecution(() onSuccessRedirect(data?.data?.id)); } ... resolve(data); }) .catch(reject);透传变量variables中组装了values、resource、合并后的 metameta与mutationMeta、dataProviderName、invalidates以及 edit 模式专属的id、mutationMode、undoableTimeout、optimisticUpdateMap——这正是文档中invalidates、mutationMode、metaData各属性能够生效的落点。successNotification/errorNotification也在此处一并传给 mutation由下游 Hook 在响应到达后调用notificationProvider。类型定义与文档参数表的对应useForm 的类型定义文件 中每个文档参数的注释与默认值都有精确对应actiondefault Action that it reads from route otherwise create is usedresourcedefault Resource name that it reads from routeiddefault Id that it reads from the URLredirectdefault list类型为show | edit | list | create | falsemutationModedefault pessimistic*带*号表示该默认值来自RefineContext即可以在Refine组件上设置全局默认值本地传入值优先undoableTimeoutdefault 5000*undoable 模式下撤销窗口的等待时长invalidatesdefault [list, many, detail]可选值包括all、resourceAll、list、many、detail、false。说明3.x 文档中的metaData属性在当前仓库源码中已演进为meta/queryMeta/mutationMeta三段式命名分别作用于全局 meta 合并、useOne查询与 mutation语义与原文档一致。测试用例对文档行为的验证useForm 的测试文件 对文档描述的关键行为做了逐条验证例如fetches data when in edit modeedit 路由下挂载useForm等待formLoading变为 false 后断言query.data.data.title等于 mock 的 posts 首条记录——验证了挂载时用 useOne 拉取记录填充表单correctly reads id value from route断言result.current.id等于路由中的1——验证了 id 的路由推断uses the correct meta values when fetching data通过 mockgetOne断言meta合并结果同时包含meta与queryMeta的键——验证了 meta 合并逻辑。这些测试均运行在MockJSONServer mock routerProvider 之上与文档中dataProvider 的 create/update/getOne 被按 action 调用的描述一致。FAQ如何失效其他资源Invalidate other resourcesinvalidates只作用于当前resource。若要失效与当前资源无直接关系的其他资源可以配合useInvalidateHook在onMutationSuccess中调用import { useInvalidate, useForm } from pankod/refine-core; const PostEdit () { const invalidate useInvalidate(); useForm({ onMutationSuccess: (data, variables, context) { invalidate({ resource: users, invalidates: [resourceAll], }); }, }); // --- };如何在提交到 API 之前修改表单数据有时需要把用户在界面上分开填写的值合并后再发送。例如将name与surname两个输入合并为fullNameimport React, { useState } from react; import { useForm } from pankod/refine-core; export const UserCreate: React.FC () { const [name, setName] useState(); const [surname, setSurname] useState(); const { onFinish } useForm(); const onSubmit (e) { e.preventDefault(); const fullName ${name} ${surname}; onFinish({ fullName: fullName, name, surname, }); }; return ( form onSubmit{onSubmit} input onChange{(e) setName(e.target.value)} / input onChange{(e) setSurname(e.target.value)} / button typesubmitSubmit/button /form ); };官方示例与进阶用法autoSave仓库内置的 form-core-use-form 示例 是本文用法的完整落地。其创建页 create.tsx 展示了文档中useFormIPost, HttpError, FormValues泛型用法的一个实用细节同一个组件同时处理 create 与 clone当 action 为 clone 时query返回值非空用它初始化defaultValueexport const PostCreate: React.FC () { const { formLoading, onFinish, query: queryResult } useFormIPost, HttpError, FormValues(); // if action is clone, well have defaultValues const defaultValues queryResult?.data?.data; const submit (event: React.FormEventHTMLFormElement) { event.preventDefault(); const formData new FormData(event.currentTarget); onFinish({ title: formData.get(title) as string, content: formData.get(content) as string, }).catch(() {}); }; // ... };其编辑页 edit.tsx 则演示了 3.x 文档之后仓库新增的autoSave自动保存能力export const PostEdit: React.FC () { const { formLoading, onFinish, query: queryResult, autoSaveProps, onFinishAutoSave, } useFormFormValues({ autoSave: { enabled: true, }, }); // ... return ( div AutoSaveIndicator {...autoSaveProps} / form onSubmit{(event) submit(event)} onChange{(event) submit(event, true)} {/* 表单字段 ... */} /form /div ); };从源码看onFinishAutoSave是经过asyncDebounce默认 1000ms防抖包装的onFinish且强制附带isAutosave: trueautoSave 的 mutation 会自动关闭 notification 与缓存失效invalidates: []避免每次输入都弹通知或刷缓存配合autoSave.invalidateOnUnmount组件卸载时会统一触发一次失效。AutoSaveIndicator组件则基于autoSavePropsstatus/data/error展示已保存/失败/保存中的指示器。泛型参数说明Type Parameters参数描述默认值TData查询结果数据类型扩展BaseRecordBaseRecordTError自定义错误对象扩展HttpErrorHttpErrorTVariables提交参数的值类型{}注意带*标记的属性如mutationMode、undoableTimeout默认值来自RefineContext即可以在Refine组件上设置全局默认useForm本地传入的值会覆盖全局值。版本说明与适用前提本文正文以 version-3.xx.xx 文档 为准该版本对应pankod/refine-core包名与 react-query v4 时代的 API返回queryResult/mutationResult属性。当前仓库主干为refinedev/corev5基于 TanStack Query v5源码中的实现对本文内容有如下可直接确认的演进返回值由queryResult/mutationResult重命名为query/mutation官方示例中已使用新命名新增autoSave含enabled、debounce、invalidateOnUnmount、invalidateOnClose子选项与overtime超时计时能力metaData演进为meta/queryMeta/mutationMeta三段式新增 4 个泛型参数TData、TResponse等以覆盖 select 与响应类型的映射。如果你正在按 3.x 文档迁移到新版建议以 当前源码类型定义 中的 JSDoc 注释作为参数默认值的最终依据。小结useForm是 Refine 数据层表单能力的编排者它不碰表单状态而是把路由推断 → 按 action 选择 useOne/useCreate/useUpdate → mutation → 通知 → 重定向 → 缓存失效这条链路封装成一个 Hook。理解它的三种 action 工作流、redirect/invalidates/mutationMode三类控制参数以及onFinish返回 Promise 的语义就能在 Headless 场景下自由组合任意 UI 框架搭建表单页面而仓库中的 form 源码、测试用例 与 form-core-use-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),仅供参考