首页
/ antd Modal 对话框组件完全指南:从声明式用法到静态方法与 Hooks 确认框

antd Modal 对话框组件完全指南:从声明式用法到静态方法与 Hooks 确认框

2026-09-07 09:58:49作者:何举烈Damon

Modal(对话框)是 antd 反馈类组件中承载“二次确认与关键操作”的核心载体。本指南基于 components/modal/index.zh-CN.md 的完整 API 文档,并结合 Modal.tsxconfirm.tsxuseModal/index.tsx 等源码实现,系统讲解三套 API 形态(受控组件、静态方法、useModal Hooks)的用法差异、全部关键属性、焦点与遮罩等新能力,以及 context 丢失等高频 FAQ 的底层成因与解法,帮助你写出真正可用、可维护的确认弹窗。

一、何时使用 Modal

当用户需要处理某个事务(提交、删除、确认重要变更),又不希望跳转页面打断当前工作流时,可以在当前页面正中打开一个浮层来承载操作——这正是 Modal 的核心使用场景。antd 提供两种入口:

  1. 受控组件形态:直接渲染 <Modal open={...} title="..." onOk={...} onCancel={...}>,适用于页面内嵌的复杂表单、详情展示等需要自定义内部结构的内容。
  2. 语法糖形态:通过 App.useAppModal.confirm 等静态方法弹出一个“问一句就够”的简洁确认框,免去维护大量样板状态。

对应的可运行示例分别位于 demo/basic.tsx(基础用法)与 demo/confirm.tsx(静态确认框)。

二、代码演示概览

组件文档通过一组 demo 覆盖了从入门到高级的完整能力矩阵,源码都位于 components/modal/demo 目录下,可作为直接的“抄作业”素材:

能力主题 演示文件 引入版本
基本受控用法、异步关闭 basic.tsxasync.tsx -
自定义页脚 / 页脚渲染函数 footer.tsxfooter-render.tsx footer 函数式 5.9.0
遮罩细分控制(enabled/blur/closable) mask.tsx mask.closable 6.3.0
加载骨架屏 loading.tsx 5.18.0
Hooks 上下文弹窗 hooks.tsx -
国际化 locale.tsx -
手动 update / destroy、自定义位置与宽度 manual.tsxposition.tsxwidth.tsx width 支持 Breakpoint 5.23.0
按钮属性、自定义渲染 button-props.tsxmodal-render.tsx -
静态方法 / 静态确认框 / 路由销毁 static-info.tsxconfirm.tsxconfirm-router.tsx -
语义化结构与 style style-class.tsx 6.0.0
动画原点、调试用线框等 custom-mouse-position.tsx -

其中 darknested_InternalPanelDoNotUseOrYouWillBeFired(见 render-panel.tsx)、wireframecomponent-token 属于 debug 性质,仅供内部开发调试理解实现,不建议作为业务模板。

三、Modal 组件 API 详解(声明式受控用法)

声明式 API 的完整类型定义见 components/modal/interface.tsModalProps),其本质是在 @rc-component/dialogDialogProps 之上扩展业务字段后由 Modal.tsx 消费。

通用属性参考:通用属性

参数 说明 类型 默认值 版本/全局配置
afterClose Modal 完全关闭后的回调(关闭动画结束后触发) function - ×
cancelButtonProps 取消按钮 props,透传给 Button,可覆写 disabledsize ButtonProps - 全局配置 6.0.0
cancelText 取消按钮文字 ReactNode 取消 ×
centered 垂直居中展示(源码中会追加 ${prefixCls}-centered class,见 Modal.tsx boolean false 全局 5.24.0
classNames 语义化结构自定义 class,支持对象或函数签名(详见下文 Semantic DOM) Record<SemanticDOM, string> | (info) => Record<SemanticDOM, string> - 5.10.0
closable 是否显示右上角关闭按钮,传对象时可同时配置 closeIcon、onClose 等 boolean | ClosableType true 5.16.0
closeIcon 自定义关闭图标;设为 null/false 隐藏关闭按钮 ReactNode <CloseOutlined /> 5.14.0
confirmLoading 确定按钮 loading(点击 ok 后进入 loading,可配合异步流程防重复提交) boolean false ×
destroyOnClose 已废弃,请改用 destroyOnHidden boolean false ×
destroyOnHidden 关闭时销毁 Modal 内子元素,保证每次打开重新挂载 boolean false 5.25.0
focusTriggerAfterClose 已废弃,请用 focusable.focusTriggerAfterClose 替代 boolean true 4.9.0
footer 底部内容;设为 footer={null} 去掉底部按钮区 ReactNode | (originNode, { OkBtn, CancelBtn }) => ReactNode 确定/取消按钮 函数式 5.9.0
forceRender 强制渲染(打开前就渲染内部结构,便于预先获取内部 DOM/实例) boolean false ×
focusable 对话框焦点管理:{ trap?: boolean, focusTriggerAfterClose?: boolean } object - 6.2.0(全局 6.4.0)
getContainer Modal 挂载节点;默认全屏展示在 document.body;false 表示挂载在当前 DOM 位置 HTMLElement | () => HTMLElement | Selectors | false document.body ×
keyboard 是否支持 Esc 键关闭 boolean true ×
mask 遮罩配置,支持 { enabled?: boolean, blur?: boolean, closable?: boolean },对象形式由 useMergedMask 解析合并(见 Modal.tsx boolean | object true 6.0.0,closable 6.3.0
maskClosable 已废弃,请使用 mask.closable 替代 boolean true -
modalRender 自定义渲染对话框:(node) => ReactNode,可在外层包裹拖拽、缩放等能力(源码中会额外包一层 ${prefixCls}-render function - 4.7.0
okButtonProps 确定按钮 props ButtonProps - 全局 6.0.0
okText 确定按钮文字 ReactNode 确定 ×
okType 确定按钮类型 string primary ×
style 设置浮层样式(如调整位置、边距),注意它作用于根容器而非 body CSSProperties - 全局 5.7.0
styles 语义化结构行内 style,支持对象或函数 Record<SemanticDOM, CSSProperties> | (info) => ... - 5.10.0
loading 显示骨架屏(内部渲染 4 行 Skeleton,见 Modal.tsx boolean false 5.18.0
scrollLock 打开时是否锁定 body 滚动 boolean true 6.5.0
title 标题 ReactNode - ×
open 是否可见(受控属性,antd 5.x 已统一从 visible 迁移到 open boolean false ×
width 宽度;传对象可按响应式断点设置不同宽度(Breakpoint,实现上转换为 CSS 变量,见 Modal.tsx string | number | Breakpoint 520 Breakpoint 5.23.0
wrapClassName 对话框外层容器(wrapper)类名 string - ×
zIndex 设置 Modal 的 z-index(默认经 useZIndex 从上下文读取,见 Modal.tsx number 1000 ×
onCancel 点击遮罩/右上角叉/取消按钮时的回调 function(e) - ×
onOk 点击确定按钮时的回调 function(e) - ×
afterOpenChange 打开与关闭动画结束后的回调 (open: boolean) => void - 5.4.0

使用注意事项

  • 默认不销毁内容<Modal /> 关闭后内部状态不会自动清空。若希望每次打开都是全新内容,请设置 destroyOnHidden。关闭动画期间内容会被 memo,这也是 FAQ 中“关闭时内容不更新”现象的根源。
  • 与 Form 配合:即使设置 destroyOnHidden,关闭时表单字段数据也不会自动销毁,还需要对 <Form> 设置 preserve={false}
  • RTL 模式Modal.method() 的 RTL(direction: rtl)支持仅限 hooks 用法useModal),静态方法不受 ConfigProvider 的 direction 上下文影响。

遮罩(mask)新形态

mask 在较新版本已升级为对象配置:

<Modal
  mask={{
    enabled: true,   // 是否显示遮罩
    blur: true,      // 是否对遮罩应用毛玻璃模糊效果(配合 blur 相关 class)
    closable: true,  // 点击遮罩是否关闭(替代旧的 maskClosable)
  }}
  ...
/>

若同时传入旧的 maskClosable,开发环境下控制台会给出废弃提示(见 Modal.tsx 中的 warning.deprecated 列表),新版代码请统一使用 mask.closable

ClosableType

closable 为对象时支持以下字段:

参数 说明 类型 默认值
afterClose Modal 完全关闭后的回调 function -
closeIcon 自定义关闭图标 ReactNode undefined
disabled 关闭图标是否禁用(禁用时关闭按钮不可点击,源码中经 useClosable 得到 closeBtnIsDisabled boolean false
onClose 弹窗即时关闭回调(点击关闭立即触发,无需等待动画) Function undefined
<Modal
  closable={{
    disabled: false,
    closeIcon: <CustomIcon />,
    onClose: () => console.log('立即关闭'),
    afterClose: () => console.log('动画结束后关闭'),
  }}
>
  ...
</Modal>

四、静态方法:Modal.method()

除受控组件外,Modal 还以命名空间方式挂载了 5 个静态确认函数(挂载逻辑见 components/modal/index.tsx):

  • Modal.info
  • Modal.success
  • Modal.error
  • Modal.warning(同时保留旧别名 Modal.warn
  • Modal.confirm

它们都接收一个 object 参数,底层统一由 confirm.tsxconfirm(config) 入口渲染确认对话框(withInfo/withSuccess 等只是把对应的 type 预置好),参数即 ModalFuncProps,类型见 interface.ts

参数 说明 类型 默认值 版本
afterClose Modal 完全关闭后的回调 function - 4.9.0
autoFocusButton 已废弃,请使用 focusable.autoFocusButton 替代 null | ok | cancel ok -
cancelButtonProps 取消按钮 props ButtonProps - -
cancelText 取消按钮文字 string 取消 -
centered 垂直居中展示 boolean false -
className 容器类名 string - -
closable 是否显示右上角关闭按钮 boolean | ClosableType false -
closeIcon 自定义关闭图标 ReactNode undefined 4.9.0
content 内容 ReactNode - -
focusable.autoFocusButton 指定自动获得焦点的按钮 null | ok | cancel ok 6.2.0
footer 底部内容,footer: null 可去掉 ReactNode | 函数 - 函数式 5.9.0
getContainer 挂载 HTML 节点,false 为挂载在当前 DOM(静态方法不支持 false,开发环境会告警,见 confirm.tsx HTMLElement | ()=>HTMLElement | Selectors | false document.body -
icon 自定义图标 ReactNode <ExclamationCircleFilled /> -
keyboard 是否支持 Esc 关闭 boolean true -
mask 遮罩配置 boolean | object true -
maskClosable 已废弃,请用 mask.closable 替代 boolean false -
scrollLock 打开时锁定 body 滚动 boolean true 6.5.0
okButtonProps 确定按钮 props ButtonProps - -
okText 确定按钮文字 string 确定 -
okType 确定按钮类型 string primary -
style 浮层样式 CSSProperties - -
title 标题 ReactNode - -
width 宽度 string | number 416(确认框默认更窄) -
wrapClassName 外层容器类名 string - 4.18.0
zIndex z-index number 1000 -
onCancel 点击取消回调,参数为关闭函数;返回 Promise 时 resolve 正常关闭、reject 不关闭 function(close) - -
onOk 点击确定回调,参数为关闭函数;返回 Promise 语义同上 function(close) - -

一个带 Promise 异步确认的经典写法:

Modal.confirm({
  title: '确认删除这条数据吗?',
  content: '删除后不可恢复,请谨慎操作。',
  okText: '确认删除',
  okType: 'danger',
  onOk() {
    return new Promise((resolve, reject) => {
      // 模拟异步请求:成功 resolve 关闭,失败 reject 保持打开
      setTimeout(resolve, 2000);
    }).catch(() => console.log('Oops errors!'));
  },
  onCancel() {},
});

返回实例的 update / destroy

函数调用后会返回一个引用,通过该引用可以更新和关闭弹窗:

const modal = Modal.info();

modal.update({
  title: '修改的标题',
  content: '修改的内容',
});

// 4.8.0 及以上:支持传入函数,基于上一个配置做增量更新
modal.update((prevConfig) => ({
  ...prevConfig,
  title: `${prevConfig.title}(新)`,
}));

modal.destroy();

update 的实现位于 confirm.tsx:函数参数会基于 currentConfig 计算出新配置,对象参数则浅合并;随后触发一次 scheduleRender,通过内部持有的 containerdocument.createDocumentFragment)重新渲染。而 destroy 本质是把 open 置为 false 并等待动画结束后的 afterClose 中卸载。

Modal.destroyAll():路由场景的批量销毁

Modal.destroyAll() 可以一次性销毁所有弹出的确认窗(info/success/error/warning/confirm)。它通常配合路由监听使用,解决路由前进、后退时确认框“残留”的问题——对“被动关闭”场景(路由变化)远比到处手动调用实例的 destroy() 更省心(modal.destroy() 更适用于主动关闭)。

import { browserHistory } from 'react-router';

// router change
browserHistory.listen(() => {
  Modal.destroyAll();
});

其底层机制值得了解:每次 confirm() 创建弹窗时,会把对应的 close 函数 push 进全局数组 destroyFns(见 confirm.tsx);destroyAll 则循环弹出并依次调用,见 index.tsxdestroyFns.ts

五、Modal.useModal():带 Context 的 Hooks 弹窗

当弹窗内容需要访问 Context(如主题、国际化、Redux 状态)时,直接调用静态方法是拿不到的(原因见 FAQ)。此时应使用 Modal.useModal

const [modal, contextHolder] = Modal.useModal();

React.useEffect(() => {
  modal.confirm({
    // ...
  });
}, []);

return <div>{contextHolder}</div>;

Hooks 形态的实现位于 useModal/index.tsx:内部维护一个 ElementsHolder 组件并通过 usePatchElement 把确认框作为普通 React 元素 patch 进 contextHolder,因此弹窗天然处于你书写 contextHolder 位置的 React 树中,能获取该位置的全部上下文(源码在渲染层面则复用 HookModal.tsx 封装 ConfirmDialog)。其 destroy/update 与静态方法实例一致,且针对 Modal 尚未挂载完成的场景用 actionQueue 做了动作排队。

modal.confirm 返回对象还额外支持 Promise 链式调用(then),这是 Hooks 版本独有的能力:

  • destroy:销毁当前弹窗
  • update:更新当前弹窗
  • then:Promise 链式调用,支持 await。点击 onOk 时 resolve true,点击 onCancel 时 resolve false;此方法会把弹窗切换为“静默”模式(silent,避免 await 场景下再走二次确认逻辑,见 useModal/index.tsx
const confirmed = await modal.confirm({ ... });
if (confirmed) {
  // 用户点击了确定
} else {
  // 用户点击了取消/关闭
}

对于业务中普遍存在“确认后还要用上下文发请求/弹 Toast”的场景,官方还推荐直接用 App 组件App.useApp())包裹,可省去手动植入 contextHolder 的样板,messagenotificationmodal 三者统一由 Context 注入。

六、Semantic DOM:按语义结构精准定制样式

从 5.10.0 起,Modal 通过 classNames/styles 支持按“语义化节点”精准定制(区别于用 :global 硬改或给整弹窗套类名)。语义结构与演示代码见 demo/_semantic.tsx,完整的语义 key 定义在 interface.ts

  • root:根节点
  • header / title:头部区域与标题
  • body:内容主体区
  • footer:底部按钮区
  • container / wrapper:容器与全屏包裹层
  • mask:遮罩层
  • close:右上角关闭按钮

classNames/styles 既支持普通对象,也支持函数签名 (info: { props }) => Record<...>,便于根据组件状态(如 openconfirmLoading)动态返回样式。合并逻辑由 useMergeSemantic 完成,且会与 ConfigProvider 中全局配置的 classNames/styles 进行合并(见 Modal.tsx)。6.0.0 起还提供了 style-class.tsx 演示更完整的语义样式用法。

同时需注意,旧的 bodyStylemaskStyle 等平面化属性已废弃,统一迁移到 styles.bodystyles.mask

七、主题变量(Design Token)

Modal 的样式 Token(如 contentBgheaderBgtitleColorfooterBgcontentLineHeight 等,由 components/modal/style 下的样式实现消费)可通过 <ComponentTokenTable component="Modal"> 在组件文档页实时查看,并借助 ConfigProvider 的 theme.components.Modal 做全局定制。相关示例可参考 demo/component-token.tsxdemo/wireframe.tsx

八、FAQ:高频问题与底层原理

1. 为什么 Modal 关闭时,内容不会更新?

Modal 在关闭动画过程中会把内容 memo 起来,避免关闭瞬间内容“跳动”导致视觉突兀。因此,如果你依赖“关闭时重置 Form 的 initialValues”这种方式刷新内容,是无效的;正确做法是在关闭后的 effect 中调用 form.resetFields() 显式重置,或使用 destroyOnHidden 让内容在关闭后真正卸载重建。

2. 为什么静态 Modal 方法拿不到 context、redux 和 ConfigProvider 的 locale/prefixCls/theme?

直接调用 Modal.confirm 等静态方法时,antd 会在组件树之外通过独立容器动态渲染出全新的 React 实例(见 confirm.tsx:创建 document.createDocumentFragment 后直接 render 到该容器),它与你当前代码所在的 React 树是两套实体,因此自然取不到调用处的 Context。静态方法能拿到的全局信息仅限 Modal.config 设置的 rootPrefixCls,该函数本身也已标记废弃,推荐改用 ConfigProvider.config(见 confirm.tsx)。

需要 Context 时请改用 Modal.useModal 返回的 modal + contextHolder,并把 contextHolder 放到你希望它继承上下文的 JSX 位置:

const [modal, contextHolder] = Modal.useModal();

return (
  <Context1.Provider value="Ant">
    {/* contextHolder 位于 Context1 内:弹窗能拿到 Context1 的 context */}
    {contextHolder}
    <Context2.Provider value="Design">
      {/* contextHolder 位于 Context2 外:弹窗拿不到 Context2 的 context */}
    </Context2.Provider>
  </Context1.Provider>
);

异同小结:Hooks 创建的 contextHolder 必须真实插入到子元素节点中才生效;而当你确实不需要上下文时,直接调用静态方法更省事。两者共存的完整链路也可对照 demo hooks.tsx

3. 静态方法如何设置 prefixCls?

通过 ConfigProvider.config 全局设置(其内部会写入 rootPrefixCls,静态弹窗渲染时据此拼出 ${rootPrefixCls}-modal 前缀,见 confirm.tsx)。

4. 动画原点来自哪里?

Modal 打开时的 zoom 动画默认从鼠标点击位置展开:源码在 document 上注册了捕获阶段的 click 监听,记录 100ms 内的最后一次点击坐标作为 mousePosition(见 Modal.tsx);如不想要该效果,可通过 mousePosition 传入自定义原点或 null 关闭。

结语:三套 API 怎么选

一句话总结实践取舍:

  • 页面内嵌、内容复杂(表单、大段信息) → 使用受控 <Modal>,配 destroyOnHiddenconfirmLoadingfooter 定制与 classNames/styles 语义化样式;
  • 简单确认、不需要 ContextModal.confirm/info/success/error/warning 静态方法,用返回值 update/destroy 精细控制,路由跳转场景用 Modal.destroyAll()
  • 需要 Context(主题/国际化/redux)、或想用 await modal.confirm()Modal.useModal()App.useApp(),把 contextHolder 插入组件树对应位置。

掌握这三种形态与背后 Modal.tsxconfirm.tsxuseModal/index.tsx 的实现脉络,就足以应对从“页面弹窗”到“全局确认”的绝大多数交互场景。

登录后查看全文
热门项目推荐
相关项目推荐