Refine v5 useModalForm 完全指南:在 Ant Design 弹窗中构建 create / edit / clone 表单
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
useModalForm是 Refine v5 中@refinedev/antd包提供的高阶表单 Hook,用于把 CRUD 表单放进 Ant Design<Modal>的全部能力,并额外返回弹窗的modalProps、开关状态与显隐控制函数,让你用最少样板代码实现"点击按钮 → 弹出 Modal → 提交表单 → 自动关闭并刷新列表"的完整交互闭环。读完本文,你将掌握 create / edit / clone 三种模式的标准写法、全部可选参数与返回值,并能结合源码理解其内部实现原理。
useModalForm 是什么
useModalForm允许你在<Modal>扩展而来,因此useForm的所有特性(数据获取、提交、mutation mode、redirect 等)在useModalForm中均可直接使用。
从源码可以确认两者的继承关系:在 useModalForm.ts 中,useModalForm直接调用了useForm,并把useForm返回的结果通过...useFormProps透传出去;其返回值类型UseModalFormReturnType是UseFormReturnType剔除saveButtonProps与deleteButtonProps后再叠加弹窗专用字段(open、close、show、modalProps等)得到的。
const { modalProps, formProps, show, close, formLoading, } = useModalForm<IPost>({ action: "create", });核心用法非常简单:modalProps直接展开到<Modal>上,formProps直接展开到<Form>上,show(id?)负责打开弹窗。
Usage:三种内置动作模式
下面通过"create"、"edit"、"clone"三个示例展示useModalForm的典型用法。三个模式都由action属性驱动,配合表格场景中的按钮触发。
create:新建记录
创建模式下,点击列表上方的创建按钮即可打开空白表单弹窗。通过List组件的createButtonProps.onClick调用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 } = useTable<IPost>(); const { modalProps: createModalProps, formProps: createFormProps, show: createModalShow, } = useModalForm<IPost>({ action: "create", }); return ( <> <List // createButtonProps 让我们可以在表格上方创建并管理一个按钮, // 点击按钮时触发 <Modal> 显示 createButtonProps={{ onClick: () => { createModalShow(); }, }} > <Table {...tableProps} rowKey="id"> <Table.Column dataIndex="id" title="ID" /> <Table.Column dataIndex="title" title="Title" /> <Table.Column dataIndex="status" title="Status" /> </Table> </List> <Modal {...createModalProps}> <Form {...createFormProps} layout="vertical"> <Form.Item label="Title" name="title" rules={[{ required: true }]} > <Input /> </Form.Item> <Form.Item label="Status" name="status" 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"; }edit:编辑记录
Refine 不会自动为列表中的每行记录添加<EditButton />,需要你在表格的 Actions 列手动放置。点击<EditButton />时通过show(record.id)传入记录 id,弹窗中的编辑表单即可据此拉取该条记录的数据。
const { modalProps: editModalProps, formProps: editFormProps, show: editModalShow, } = useModalForm<IPost>({ action: "edit", warnWhenUnsavedChanges: true, });<Table.Column<IPost> title="Actions" dataIndex="actions" key="actions" render={(_, record) => ( <Space> <EditButton hideText size="small" recordItemId={record.id} onClick={() => editModalShow(record.id)} /> </Space> )} />注意:必须把记录的"id"传给show,edit与clone表单都需要依赖该 id 获取记录数据。源码中的handleShow也印证了这一约束——当action为"edit"或"clone"时,只有传入(或已存在)id 才会真正打开弹窗,见 useModalForm.ts。
clone:克隆记录
克隆模式用于基于已有记录快速创建一条新数据。同样需要手动在列表中加入<CloneButton />并传入记录 id:
const { modalProps: cloneModalProps, formProps: cloneFormProps, show: cloneModalShow, } = useModalForm<IPost>({ action: "clone", });<Table.Column<IPost> title="Actions" dataIndex="actions" key="actions" render={(_, record) => ( <Space> <CloneButton hideText size="small" recordItemId={record.id} onClick={() => cloneModalShow(record.id)} /> </Space> )} />clone 模式会预填原记录的数据,提交时走 create 逻辑,生成一条全新的记录。
Properties:配置弹窗表单的开关与默认值
useModalForm继承useForm的全部 props(详见 use-form 文档的 Properties 小节),并额外提供以下弹窗专属配置。所有默认值都能在 useModalForm.ts 的解构赋值中直接找到依据。
syncWithLocation
当syncWithLocation为true时,弹窗的可见状态以及记录的id会与 URL 同步,默认值为false。这带来两个实际收益:刷新页面后弹窗状态不丢失,且弹窗状态可以作为可分享的链接。
该属性也可以写成对象形式{ key: string; syncId?: boolean }来定制 URL 查询参数的 key;只有当syncId为true时,id才会同步到 URL:
const modalForm = useModalForm({ syncWithLocation: { key: "my-modal", syncId: true }, });从源码看,未指定key时默认的查询参数名为`modal-${identifier}-${action}`(例如modal-posts-edit),内部通过useParsed读取 URL 参数、通过useGo把{ open: true, id }写回 URL,见 useModalForm.ts。测试用例should meta[syncWithLocationKey] overrided by default也验证了syncWithLocation: true时getOne请求会携带meta: { "modal-posts-edit": undefined },见 index.spec.tsx。
defaultFormValues
表单的默认值,用于预填需要展示的数据:
useModalForm({ defaultFormValues: { title: "Hello World", }, });它也可以传入一个 async 函数来异步获取默认值,加载状态通过返回的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
设为true时弹窗默认显示,默认值为false:
const modalForm = useModalForm({ defaultVisible: true, });autoSubmitClose
提交成功后是否自动关闭弹窗,默认值为true:
const modalForm = useModalForm({ autoSubmitClose: false, });autoResetForm
提交成功后是否重置表单,默认值为true:
const modalForm = useModalForm({ autoResetForm: false, });autoResetFormWhenClose
弹窗关闭时是否重置表单,默认值为true:
const modalForm = useModalForm({ autoResetFormWhenClose: false, });warnWhenUnsavedChanges
设为true后,当用户带着未保存的修改离开页面时会弹出警告,防止误操作丢失数据,默认值为false。也可以在<Refine>组件中统一配置:
const modalForm = useModalForm({ warnWhenUnsavedChanges: true, });源码中该警告通过window.confirm弹出,文案默认是 "Are you sure you want to leave? You have unsaved changes.",支持通过 i18n 的warnWhenUnsavedChanges键翻译,见 useModalForm.ts。
overtimeOptions
当请求耗时过长时,可以通过overtimeOptions显示加载提示。interval是毫秒级的检查间隔,onInterval是每个间隔触发的回调。Hook 返回overtime对象,elapsedTime为已耗时毫秒数,请求完成后变为undefined:
const { overtime } = useModalForm({ //... overtimeOptions: { interval: 1000, onInterval(elapsedInterval) { console.log(elapsedInterval); }, }, }); console.log(overtime.elapsedTime); // undefined, 1000, 2000, 3000 4000, ... // 实际使用方式: { elapsedTime >= 4000 && <div>this takes a bit longer than expected</div>; }autoSave
如果希望在用户编辑表单后延时自动保存,可以启用autoSave.enabled。默认情况下 autoSave 不会使查询失效,但可以通过invalidateOnUnmount与invalidateOnClose在卸载或关闭时让查询失效。它还支持onMutationSuccess与onMutationError回调,回调中可用isAutoSave参数判断 mutation 是否由 autoSave 触发。
autoSave 只作用于edit模式:编辑数据时改动会自动保存;而创建新数据时仍需手动提交。
enabled
启用 autoSave,默认false:
useModalForm({ autoSave: { enabled: true, }, });debounce
设置 autoSave 的防抖时间(毫秒),默认1000:
useModalForm({ autoSave: { enabled: true, debounce: 2000, }, });onFinish
在数据发送到服务器之前修改数据:
useModalForm({ autoSave: { enabled: true, onFinish: (values) => { return { foo: "bar", ...values, }; }, }, });invalidateOnUnmount
Hook 卸载时使当前资源关联的list、many、detail查询失效,默认false。也可以通过invalidates属性选择要失效的查询类型:
useModalForm({ autoSave: { enabled: true, invalidateOnUnmount: true, }, });invalidateOnClose
弹窗关闭时使当前资源关联的list、many、detail查询失效,默认false:
useModalForm({ autoSave: { enabled: true, invalidateOnClose: true, }, });invalidateOnClose的实现位于handleClose中:当autoSaveProps.status === "success"时,调用invalidate({ id, invalidates, dataProviderName, resource })使缓存失效,见 useModalForm.ts。
Return Values:Hook 返回什么
useModalForm返回useForm的所有返回值,外加与<Modal>协作所需的额外值。
formProps
管理<Form>状态与动作所必需的 props,底层来自useForm。它包含管理 Ant Design<Form>的各类属性(onValuesChange、initialValues、onFieldsChange、onFinish等)。
注意
onFinish与formProps.onFinish的区别useModalForm直接返回的onFinish与useForm的onFinish一致;而在弹窗场景下,提交后关闭弹窗、重置字段是必须的,因此formProps.onFinish对onFinish做了扩展,在底层额外处理了弹窗关闭与字段清空。如果你需要在提交前定制数据,建议使用formProps.onFinish,把提交后的收尾工作交给它处理。
源码中formProps.onFinish的执行顺序是:先await onFinish(values)完成提交,再根据autoSubmitClose决定是否close(),最后根据autoResetForm决定是否form.resetFields(),见 useModalForm.ts。
modalProps
<Modal>中统一组装:
| 属性 | 说明 | 默认值 |
|---|---|---|
title | 弹窗标题,基于资源与 action 值自动生成(如 "Edit test",见测试用例 index.spec.tsx) | 由资源名与动作组合 |
okText | 弹窗内"提交"按钮的文本 | "Save" |
cancelText | 弹窗内"取消"按钮的文本 | "Cancel" |
width | 弹窗宽度 | 1000px |
forceRender | 是否立即渲染弹窗(而非懒渲染) | true |
okButtonProps | "提交"按钮所需的所有 props(disabled、loading等),点击okButtonProps.onClick会触发form.submit() | 基于formLoading派生 |
onOk | 提交弹窗内<Form>的函数,适合手动提交表单 | — |
onCancel | 关闭弹窗的函数(等同于close),适合手动关闭 | — |
open
弹窗当前可见状态(boolean),默认值取决于defaultVisible。
close
手动关闭弹窗的函数。内部会依次处理:autoSave 失效、warnWhenUnsavedChanges确认、清空id、关闭弹窗、按autoResetFormWhenClose重置字段。配合手动提交的典型写法:
const { close, modalProps, formProps, onFinish } = useModalForm(); const onFinishHandler = async (values) => { // await `onFinish` 对未保存更改提示、查询失效、重定向等功能至关重要 // 如果使用 formProps.onFinish,它会在内部自动调用 close await onFinish(values); close(); }; return ( <Modal {...modalProps}> <Form {...formProps} onFinish={onFinishHandler} layout="vertical"> <Form.Item label="Title" name="title"> <Input /> </Form.Item> </Form> </Modal> );submit
手动提交表单的函数。适合自定义弹窗 footer 按钮的场景:
const { modalProps, formProps, submit } = useModalForm(); return ( <Modal {...modalProps} footer={[ <Button key="submit" type="primary" onClick={submit}> Submit </Button>, ]} > <Form {...formProps} layout="vertical"> <Form.Item label="Title" name="title"> <Input /> </Form.Item> </Form> </Modal> );show
打开弹窗的函数,可接收可选的记录id:
const { modalProps, formProps, show } = useModalForm(); return ( <> <Button type="primary" onClick={() => show()}> Show Modal </Button> <Modal {...modalProps}> <Form {...formProps} layout="vertical"> <Form.Item label="Title" name="title"> <Input /> </Form.Item> </Form> </Modal> </> );overtime
overtime对象:elapsedTime为已耗时毫秒数,请求完成后变为undefined:
const { overtime } = useModalForm(); console.log(overtime.elapsedTime); // undefined, 1000, 2000, 3000 4000, ...autoSaveProps
启用autoSave后,Hook 返回包含data、error、status三个属性的 mutation 结果对象,status取值包括"loading" | "error" | "idle" | "success"。
defaultFormValuesLoading
当defaultFormValues是 async 函数时,在该函数 resolve 之前此值为true。
FAQ:如何在提交前修改表单数据
一个常见需求:把用户填写的name与surname两个字段,合并成fullName再发送给 API。做法是利用formProps.onFinish拦截提交:
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} layout="vertical"> <Form.Item label="Name" name="name"> <Input /> </Form.Item> <Form.Item label="Surname" name="surname"> <Input /> </Form.Item> </Form> </Modal> ); };由于走的是formProps.onFinish,提交成功后弹窗关闭、表单重置等收尾逻辑依然由 Hook 自动完成。
API Reference
Type Parameters
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
TQueryFnData | 查询函数返回的结果数据,继承BaseRecord | BaseRecord | BaseRecord |
TError | 继承HttpError的自定义错误对象 | HttpError | HttpError |
TVariables | 提交参数的值 | {} | — |
TData | select函数返回的结果数据,继承BaseRecord,未指定时默认取TQueryFnData | BaseRecord | TQueryFnData |
TResponse | mutation 函数返回的结果数据,继承BaseRecord,未指定时默认取TData | BaseRecord | TData |
TResponseError | 继承HttpError的自定义错误对象,未指定时默认取TError | HttpError | TError |
Return Value
| Key | 说明 | 类型 |
|---|---|---|
show | 打开弹窗的函数 | (id?: BaseKey) => void |
formProps | 管理表单组件所需的 props | FormProps |
modalProps | 管理弹窗组件所需的 props | ModalProps |
formLoading | 表单加载状态 | boolean |
submit | 提交方法,参数为表单字段值 | () => void |
open | 弹窗是否打开 | boolean |
close | 关闭弹窗的函数 | () => void |
defaultFormValuesLoading | 默认表单值的加载状态 | boolean |
form | Ant Design 表单实例 | FormInstance<TVariables> |
id | edit 动作对应的记录 id | BaseKey \| undefined |
setId | id的 setter | Dispatch<SetStateAction<BaseKey \| undefined>> |
query | 记录查询的结果 | QueryObserverResult<{ data: TData }> |
mutation | 提交表单触发的 mutation 结果 | UseMutationResult<{ data: TData }, TError, { resource: string; values: TVariables; }, unknown> |
overtime | 超时加载 props | { elapsedTime?: number } |
autoSaveProps | 自动保存 props | { data: UpdateResponse<TData> \| undefined, error: HttpError \| null, status: "loading" \| "error" \| "idle" \| "success" } |
结合源码理解内部行为
默认值定义:defaultVisible = false、autoSubmitClose = true、autoResetForm = true、autoResetFormWhenClose = true,全部在 useModalForm.ts 的入口解构中声明,与文档描述完全一致。
mutationMode 与关闭时机:测试用例验证了不同 mutation mode 下弹窗的关闭行为——pessimistic模式会等待 mutation 成功后才关闭弹窗,而optimistic/undoable模式提交后立即关闭,见 index.spec.tsx。formProps.onFinish中await onFinish(values)正是保证该时序的关键。
基础弹窗状态管理:useModalForm底层复用了@refinedev/antd的useModal(见 useModal/index.tsx),它把核心useModal的visible映射为 Ant Design Modal 的open,并接管onCancel默认关闭行为;useModalForm再在其上叠加handleShow/handleClose完成 id 注入、未保存警告、自动重置等增强逻辑。
真实可运行示例:仓库中的 form-antd-use-modal-form 示例同时演示了 create 与 edit 两个弹窗、syncWithLocation以及独立的 Show 弹窗,完整代码见 examples/form-antd-use-modal-form/src/pages/posts/list.tsx。可通过以下命令在本地运行:
npm create refine-app@latest -- --example form-antd-use-modal-form小结
useModalForm把 Refine 的表单数据流与 Ant Design 的 Modal 弹窗无缝衔接:action: "create" | "edit" | "clone"决定表单行为,syncWithLocation让弹窗状态可被 URL 记忆,autoSave为编辑场景提供防抖自动保存,autoSubmitClose/autoResetForm/autoResetFormWhenClose三个开关精确控制提交与关闭后的收尾动作。配合源码中明确的默认值与测试用例,你可以放心地把它当作弹窗 CRUD 的标准方案。
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考