
Refine v5 实战使用 useModalForm 在 Ant Design Modal 中构建创建、编辑与克隆表单【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refineuseModalForm是 Refine v5 中refinedev/antd包提供的一个表单 Hook用于在 Ant Design 的Modal弹窗组件内完整地创建、编辑和克隆记录。本文以仓库中的示例 form-antd-use-modal-form 为主体结合 官方 useModalForm 参考文档 与 源码实现系统讲解其用法、全部配置项、返回值与底层原理帮助你在一页列表内用弹窗表单完成整套 CRUD 交互。认识 useModalFormuseModalForm允许你在Modal组件内部管理一个表单并返回 Ant DesignForm与Modal所需的全部 props。你只需要把modalProps展开到Modal、把formProps展开到Form弹窗的开关、表单的提交、数据回填、提交后关闭与重置等行为即可开箱即用。关键的一点是useModalForm是从refinedev/antd包中的useForm扩展而来的源码中通过import { useForm, type UseFormProps, type UseFormReturnType } from ../useForm;组合实现这意味着useForm的全部能力——数据加载、提交、表单校验、warnWhenUnsavedChanges、overtimeOptions、autoSave等——都可以直接在useModalForm中使用。从 useModalForm.ts 的类型定义可以看到UseModalFormProps由UseFormPropsCore、UseFormProps、useModalFormConfig、LiveModeProps、FormWithSyncWithLocationParams组合而来并额外增加了四个 Modal 专属配置export type UseModalFormProps... UseFormPropsCore... UseFormProps... useModalFormConfig LiveModeProps FormWithSyncWithLocationParams { defaultVisible?: boolean; // 默认是否可见默认 false autoSubmitClose?: boolean; // 提交成功后自动关闭默认 true autoResetForm?: boolean; // 提交成功后重置表单默认 true autoResetFormWhenClose?: boolean; // 关闭时重置表单默认 true };useModalFormConfig限定了action的取值show | edit | create | clone这是驱动整个 Hook 行为的分支开关。快速开始在列表页中实现创建弹窗我们先看最基础的创建场景。完整示例位于 examples/form-antd-use-modal-form/src/pages/posts/list.tsx其核心思路是用useTable渲染列表用action: create的useModalForm管理新增弹窗点击List自带的创建按钮时调用show()打开弹窗。import React from react; import { List, useModalForm, useTable } from refinedev/antd; import { Form, Input, Modal, Select, Table } from antd; const PostList: React.FC () { const { tableProps } useTableIPost(); // 创建弹窗action 指定为 create const { modalProps: createModalProps, formProps: createFormProps, show: createModalShow, } useModalFormIPost({ action: create, }); return ( List // createButtonProps 用于配置列表上方的新建按钮 // 点击后通过 createModalShow() 打开弹窗 createButtonProps{{ onClick: () { createModalShow(); }, }} Table {...tableProps} rowKeyid Table.Column dataIndexid titleID / Table.Column dataIndextitle titleTitle / Table.Column dataIndexstatus titleStatus / /Table /List Modal {...createModalProps} Form {...createFormProps} layoutvertical Form.Item labelTitle nametitle rules{[{ required: true }]} Input / /Form.Item Form.Item labelStatus namestatus rules{[{ required: true }]} Select options{[ { label: Published, value: published }, { label: Draft, value: draft }, { label: Rejected, value: rejected }, ]} / /Form.Item /Form /Modal / ); }; interface IPost { id: number; title: string; status: published | draft | rejected; }action: create时show()无需传id提交时useModalForm会自动调用 data provider 的create方法创建记录并在成功后根据autoSubmitClose与autoResetForm的默认行为自动关闭弹窗、清空表单。编辑弹窗通过 record id 回填数据action: edit的用法与创建几乎一致区别在于必须把当前记录的id传给show(record.id)useModalForm才会据此调用getOne拉取数据并回填表单。Refine 不会自动为列表的每一行渲染EditButton需要你手动放在操作列中import { EditButton, List, useModalForm, useTable } from refinedev/antd; import { Form, Input, Modal, Select, Space, Table } from antd; const PostList: React.FC () { const { tableProps } useTableIPost(); const { modalProps: editModalProps, formProps: editFormProps, show: editModalShow, } useModalFormIPost({ action: edit, warnWhenUnsavedChanges: true, // 有未保存修改时离开页面给出警告 }); return ( List Table {...tableProps} rowKeyid Table.Column dataIndexid titleID / Table.Column dataIndextitle titleTitle / Table.Column dataIndexstatus titleStatus / Table.ColumnIPost titleActions dataIndexactions keyactions render{(_, record) ( Space EditButton hideText sizesmall recordItemId{record.id} onClick{() editModalShow(record.id)} / /Space )} / /Table /List Modal {...editModalProps} Form {...editFormProps} layoutvertical {/* 与创建弹窗相同的 Form.Item 结构 */} /Form /Modal / ); };编辑模式下表单内部会挂载useEditForm/useForm的数据查询逻辑formProps中的initialValues会在记录数据到达后自动设置因此你不需要手动处理useEffect回填弹窗打开时会先进入加载状态可通过formLoading配合Spin显示。示例代码中正是这样处理的Modal {...editModalProps} Spin spinning{editFormLoading} Form {...editFormProps} layoutvertical.../Form /Spin /Modal注意不要忘记把记录id传给show这是edit与clone两种模式获取记录数据的必要条件。克隆弹窗复制一条已有记录action: clone与edit结构相同差异在于它会加载目标记录的数据填入表单但提交时调用的是 data provider 的create方法从而生成一条新的记录——实现以某条数据为模板新建的常见需求。同样需要手动放置CloneButton并把record.id传入showimport { CloneButton, List, useModalForm, useTable } from refinedev/antd; import { Form, Input, Modal, Select, Space, Table } from antd; const PostList: React.FC () { const { tableProps } useTableIPost(); const { modalProps: cloneModalProps, formProps: cloneFormProps, show: cloneModalShow, } useModalFormIPost({ action: clone, }); return ( List Table {...tableProps} rowKeyid {/* ...省略列表列定义... */} Table.ColumnIPost titleActions dataIndexactions keyactions render{(_, record) ( Space CloneButton hideText sizesmall recordItemId{record.id} onClick{() cloneModalShow(record.id)} / /Space )} / /Table /List Modal {...cloneModalProps} Form {...cloneFormProps} layoutvertical.../Form /Modal / ); };三个模式总结如下action 值数据来源提交行为show() 是否需要 idcreate无data provider 的create否edit按 id 调用getOne回填data provider 的update是clone按 id 调用getOne回填data provider 的create是与 URL 同步syncWithLocationsyncWithLocation是useModalForm最实用的增强能力之一。当设为true时弹窗的可见状态以及当前记录的id会与 URL 查询参数保持同步默认值为false。const modalForm useModalForm({ syncWithLocation: true, });该属性也可以配置为对象形式{ key: string; syncId?: boolean }用于自定义 URL 查询参数的 key并且只有syncId为true时id才会同步到 URLconst modalForm useModalForm({ syncWithLocation: { key: my-modal, syncId: true }, });从源码看syncWithLocation的实现非常精细useModalForm.ts#L147-L256同步 key 的默认生成规则是modal-${identifier}-${action}例如资源posts的编辑弹窗对应modal-posts-edit首次挂载时会从useParsed()解析出的 URL 参数中读取open布尔值或字符串true都会触发show()与id触发setId之后每次可见状态或id变化都会通过useGo()以type: replace的方式写回 URL可见时写入{ open: true, id }关闭时移除该参数保证浏览器前进/后退可以还原弹窗状态。在示例应用 App.tsx 中全局也开启了options.syncWithLocation: true两者配合后刷新页面时弹窗与当前编辑的记录可以完整恢复非常适合可分享 URL 的管理后台场景。核心配置项详解useModalForm继承useForm的全部 props详见 useForm 文档 的 Properties 章节这里重点讲解与弹窗行为和增强能力相关的配置。defaultFormValues用于预填充表单的默认值useModalForm({ defaultFormValues: { title: Hello World, }, });也可以传入一个异步函数来获取默认值加载期间可通过返回的defaultFormValuesLoading跟踪状态const { defaultFormValuesLoading } useModalForm({ defaultFormValues: async () { const response await fetch(https://my-api.com/posts/1); const data await response.json(); return data; }, });需要留意的是当action为edit或clone时异步defaultFormValues与记录查询之间存在竞态条件表单值将是最后完成的那次操作的结果因此在这两种模式下要谨慎使用异步默认值。defaultVisible是否默认打开弹窗默认falseconst modalForm useModalForm({ defaultVisible: true, });源码中该值会透传给内部useModal的初始open状态useModalForm.ts#L184-L188。autoSubmitClose提交成功后是否自动关闭弹窗默认trueconst modalForm useModalForm({ autoSubmitClose: false, });autoResetForm提交成功后是否重置表单字段默认trueconst modalForm useModalForm({ autoResetForm: false, });autoResetFormWhenClose弹窗关闭时是否重置表单默认trueconst modalForm useModalForm({ autoResetFormWhenClose: false, });在源码的handleClose中关闭弹窗时会setId(undefined)并调用form.resetFields()仅在autoResetFormWhenClose为 true 时确保下次打开时不会残留上一次的数据useModalForm.ts#L266-L297。warnWhenUnsavedChanges设为true后用户在带未保存修改的情况下尝试离开页面时会弹出确认警告默认false。该值也可以在Refine组件的options.warnWhenUnsavedChanges中全局设置useModalForm的局部值会覆盖全局默认值示例 App.tsx 即开启了全局配置并配合UnsavedChangesNotifier组件使用const modalForm useModalForm({ warnWhenUnsavedChanges: true, });overtimeOptions当请求耗时过长时用于展示加载超时提示。interval为轮询间隔毫秒onInterval为每次间隔触发的回调Hook 返回的overtime.elapsedTime表示已耗时毫秒请求完成后变为undefinedconst { overtime } useModalForm({ overtimeOptions: { interval: 1000, onInterval(elapsedInterval) { console.log(elapsedInterval); }, }, }); // 在渲染中这样使用 { overtime.elapsedTime 4000 divthis takes a bit longer than expected/div }autoSave自动保存功能仅在edit模式下生效当用户停止编辑超过防抖时间后自动触发保存创建模式下仍需手动提交。useModalForm({ autoSave: { enabled: true, }, });autoSave 对象支持以下子配置debounce防抖时间默认1000毫秒例如debounce: 2000表示停止输入 2 秒后保存onFinish提交前修改数据的回调例如onFinish: (values) ({ foo: bar, ...values })invalidateOnUnmountHook 卸载时是否失效当前资源的list、many、detail查询默认false可通过invalidates选择要失效的查询类型invalidateOnClose弹窗关闭时是否失效上述查询默认falseonMutationSuccess / onMutationError保存成功/失败回调可通过isAutoSave参数判断触发来源是否为 autoSave。启用后Hook 会额外返回autoSaveProps包含 mutation 的data、error、status。返回值详解useModalForm返回useForm的全部返回值外加一组弹窗专属 API返回值类型说明formPropsFormProps展开到Form内含onValuesChange、initialValues、onFinish等modalPropsModalProps展开到Modal内含open、title、okText、onOk、onCancel等show(id?: BaseKey) void打开弹窗edit/clone模式需传记录idclose() void关闭弹窗openboolean弹窗当前是否可见submit() void手动触发表单提交formLoadingboolean表单数据加载状态formFormInstanceTVariablesAnt Design 表单实例id/setIdBaseKey \| undefined当前编辑的记录 id 及其 setterqueryQueryObserverResult记录查询结果mutationUseMutationResult表单提交触发的 mutationovertime{ elapsedTime?: number }超时信息autoSaveProps对象autoSave 的 mutation 状态defaultFormValuesLoadingboolean异步默认值加载状态formPropsformProps来自底层的useForm负责管理Form的状态与行为。一个重要的区别是useModalForm直接返回的onFinish与useForm的onFinish相同而formProps.onFinish在其基础上扩展了提交后的操作——内部会依次执行onFinish(values)、按autoSubmitClose关闭弹窗、按autoResetForm重置字段。源码中这一逻辑清晰可见useModalForm.ts#L327-L338。因此如果你需要自定义提交数据推荐通过formProps.onFinish包装让 Hook 继续接管提交后的关闭与重置。modalPropsmodalProps为Modal提供完整的行为配置useModalForm.ts#L339-L354title根据资源与 action 自动生成例如Create Post、Edit Post支持 i18nkey 为${identifier}.titles.${action}okText确定按钮文案默认Savei18n keybuttons.savecancelText取消按钮文案默认Cancelwidth弹窗宽度默认1000pxforceRender是否强制渲染而非懒加载默认trueokButtonProps确定按钮的完整 propsdisabled、loading等其onClick会触发form.submit()onOk手动提交Form的函数onCancel手动关闭弹窗的函数等价于close内部会处理未保存警告与表单重置。open / close / show / submit这四个返回值用于完全手动控制弹窗生命周期。例如用close在自定义提交逻辑后手动关窗const { close, modalProps, formProps, onFinish } useModalForm(); const onFinishHandler async (values) { // Awaiting onFinish 很重要未保存更改提示、缓存失效、重定向等都依赖它 await onFinish(values); close(); }; return ( Modal {...modalProps} Form {...formProps} onFinish{onFinishHandler} layoutvertical Form.Item labelTitle nametitle Input / /Form.Item /Form /Modal );用submit在自定义 footer 中触发提交const { modalProps, formProps, submit } useModalForm(); return ( Modal {...modalProps} footer{[ Button keysubmit typeprimary onClick{submit} Submit /Button, ]} Form {...formProps} layoutvertical {/* Form.Item... */} /Form /Modal );用show()从任意按钮打开弹窗const { modalProps, formProps, show } useModalForm(); return ( Button typeprimary onClick{() show()} Show Modal /Button Modal {...modalProps}.../Modal / );底层实现原理从 useModalForm.ts 的源码可以看到整个 Hook 的组合方式基于useForm组装调用useForm拿到form、formProps、id、setId、formLoading、onFinish、autoSaveProps并把autoSave、invalidates及其余 props 原样透传内部useModal状态管理通过hooks/modal的useModal管理open状态modalProps.open初始值来自defaultVisiblehandleShow守卫edit/clone模式下只有拿到showId或已有id时才真正打开弹窗避免无 id 打开编辑框handleClose收尾autoSave 且invalidateOnClose时失效查询 → 未保存警告确认 → 清空id→ 关闭弹窗 → 按需重置表单URL 双向同步初始化时从 URL 恢复open/id状态变化时写回 URL合并返回把useForm返回值与弹窗状态合并并覆写formProps.onFinish、modalProps的默认文案与行为。仓库中还提供了针对该 Hook 的单元测试 packages/antd/src/hooks/form/useModalForm/index.spec.tsx覆盖了三种 action 下的渲染、提交与弹窗行为可作为深入理解其内部契约的参考。FAQ提交前如何修改数据需求场景用户填写了name与surname两个输入框但 API 期望提交fullName字段。此时用formProps.onFinish包装一层转换即可让 Hook 继续负责提交后的关闭与重置import { Modal, useModalForm } from refinedev/antd; import { Form, Input } from antd; import React from react; export const UserCreate: React.FC () { const { formProps, modalProps } useModalForm({ action: create, }); const handleOnFinish (values) { formProps.onFinish?.({ fullName: ${values.name} ${values.surname}, }); }; return ( Modal {...modalProps} Form {...formProps} onFinish{handleOnFinish} layoutvertical Form.Item labelName namename Input / /Form.Item Form.Item labelSurname namesurname Input / /Form.Item /Form /Modal ); };类型参数说明useModalForm支持泛型参数用于约束数据与错误类型类型参数说明默认值TQueryFnData查询函数返回的数据类型需继承BaseRecordBaseRecordTError自定义错误类型需继承HttpErrorHttpErrorTVariables提交参数类型{}TDataselect函数返回的数据类型TQueryFnDataTResponsemutation 返回的数据类型TDataTResponseErrormutation 的错误类型TError两个值得注意的默认行为标注*的 props 在RefineContext中有默认值也可在Refine组件上设置useModalForm的局部值会覆盖全局默认标注**的redirect若未显式配置action: create时默认跳转到该资源的edit页action: edit时默认跳转到list。运行示例项目示例应用 form-antd-use-modal-form 展示了创建 编辑 查看三种弹窗的完整组合list.tsx 中同时实例化了create与edit两个useModalForm并通过useShow实现只读查看弹窗。其技术栈与配置如下依赖package.jsonrefinedev/corev5、refinedev/antdv6、antdv5、reactv19、react-routerv7、refinedev/simple-restv6Node 版本要求20数据源示例通过dataProvider(API_URL)连接https://api.fake-rest.refine.devApp.tsxsimple-rest数据提供器位于 packages/simple-rest本地运行cd examples/form-antd-use-modal-form npm install npm run dev也可以在项目根目录使用 Refine CLI 直接拉取该示例npm create refine-applatest -- --example form-antd-use-modal-form启动后访问/posts页面即可看到列表上方的新建按钮、行内编辑按钮与查看按钮点击后分别弹出对应的 Ant Design Modal 表单。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考