首页
/ ant-design Message loading 全局加载提示实战:duration=0 与异步手动销毁的完整指南

ant-design Message loading 全局加载提示实战:duration=0 与异步手动销毁的完整指南

2026-09-07 20:37:52作者:滑思眉Philip

导读

Message 是 ant-design 中最常用的轻量级全局反馈组件,而在所有消息类型中,loading(加载中)是一种特殊的"常驻态"——它通常需要在耗时操作期间持续展示,并在操作结束时被异步移除。本文以仓库中 components/message/demo/loading.md 所对应的官方示例为核心,逐步拆解 loading.tsx 的完整实现:讲解 loading 类型的消息与普通消息的本质差异、duration: 0 关闭自动消失的含义、如何通过 messageApi.destroy 实现"异步自行移除",并深入到 ant-design 源码层说明 loading 图标渲染、Promise 接口与销毁机制的底层实现。阅读完本文,你将能够在自己的项目中正确实现"按钮点击后进入全局加载态、任务完成后自动消失"的经典交互。

一、这个示例要解决的问题

原文档 loading.md 对示例的描述非常简洁:

进行全局 loading,异步自行移除。(Display a global loading indicator, which is dismissed by itself asynchronously.)

这段描述其实点出了 loading 型消息的两条核心诉求:

  1. 全局 loading:通过 Message 组件在页面顶部中央展示一条非侵入式的轻量提示,与 Spin 的局部加载不同,它不阻塞任何页面区域;
  2. 异步自行移除:loading 消息出现后不会像普通消息那样"闪现后消失",而是等到后台异步任务(如请求、上传、保存)完成时由代码主动关闭它。

从 ant-design 组件的 API 文档(见 index.en-US.md)可以确认,Message 有五种消息类型:infosuccesserrorwarningloading,其中 loading 是唯一一个语义上"不表达最终结果、只表达进行中"的类型。

二、示例源码逐行拆解

官方示例的完整代码位于 loading.tsx,核心逻辑只有几十行:

import React from 'react';
import { Button, message } from 'antd';

const App: React.FC = () => {
  const [messageApi, contextHolder] = message.useMessage();

  const success = () => {
    messageApi.open({
      type: 'loading',
      content: 'Action in progress..',
      duration: 0,
    });
    // Dismiss manually and asynchronously
    setTimeout(messageApi.destroy, 2500);
  };

  return (
    <>
      {contextHolder}
      <Button onClick={success}>Display a loading indicator</Button>
    </>
  );
};

export default App;

2.1 使用 Hook 形式而非静态方法

示例通过 message.useMessage() 获取 messageApi 实例和 contextHolder 占位节点,并在 JSX 中将 {contextHolder} 渲染到组件树中。这是 ant-design 官方推荐的消息用法,比直接调用 message.loading() 静态方法多两个明显优势:

  • 消息实例能够继承当前 React 组件树的 Context(如 ConfigProviderlocaleprefixClstheme 以及自定义 Provider);
  • messageApi 在组件卸载时会随 contextHolder 一并清理,避免消息悬挂在全局节点上。

这一点在 index.en-US.md 的 FAQ 中有明确说明:调用静态方法时 antd 会动态创建独立的 React 实例,其 Context 与调用方所在位置的 Context 不同;需要 Context 信息时应使用 useMessage

2.2 通过 open 传对象配置,显式指定 type

示例使用 messageApi.open({...}) 而非 messageApi.loading(content) 的快捷方法。两者在底层是等价的——从 useMessage.tsx 的源码看,useInternalMessage 内部为 infosuccesswarningerrorloading 五种类型统一生成了 typeOpen 包装函数,最终都会把 type 合并进 config 后调用 open(mergedConfig)

const keys: NoticeType[] = ['info', 'success', 'warning', 'error', 'loading'];
keys.forEach((type) => {
  const typeOpen: TypeOpen = (jointContent, duration, onClose) => {
    ...
    const mergedConfig = { onClose: mergedOnClose, duration: mergedDuration, ...config, type };
    return open(mergedConfig);
  };
  clone[type] = typeOpen;
});

interface.ts 中,ArgsProps.type 的类型定义为 'info' | 'success' | 'error' | 'warning' | 'loading'。因此对象式配置中的 type: 'loading' 是让消息按 loading 语义渲染的关键开关。

三、duration: 0:让 loading 消息永不自动消失

3.1 duration 的默认行为与取 0 的含义

普通消息默认在展示 3 秒后自动消失。这个默认值在源码中有明确体现——useMessage.tsx 顶部定义了:

const DEFAULT_DURATION = 3;

并在 Holder 组件解构 props 时作为 duration 的默认值使用。API 文档的 config 参数表(见 index.en-US.md)也写明:

参数 说明 类型 默认值
duration 自动关闭的延时,单位秒,设为 0 时不自动关闭 number 3

duration: 0 是本示例最关键的配置:它关闭了消息的自动消失计时器,使 loading 消息一直停留在页面上,直到代码手动调用销毁方法。这正是"进行全局 loading"能成立的前提——如果保留默认的 3 秒,提示会在异步任务还没完成时就不见了。

3.2 静态方法调用中的等价写法

如果使用静态方法形式,duration 可以作为第二个参数传入,同样传 0 表示不自动关闭:

message.loading('Action in progress..', 0);

这个调用形式在 message/index.test.tsx 的测试用例中大量出现,例如 it('should hide message correctly') 用例中通过 message.loading('Action in progress..', 0) 打开一条不自动关闭的消息,再断言 .ant-message-notice 节点确实渲染出来。

3.3 相关配置项 pauseOnHover

需要注意,即使设置了有限的 duration,antd 还提供 pauseOnHover(默认 true)——鼠标悬停在消息上时暂停计时,移开后继续。对 loading 场景而言,由于通常配合 duration: 0 使用,计时器本身并未启动,因此该配置主要影响那些"既想持续展示、又设置了超时上限"的混合场景。

四、loading 图标从哪来:PurePanel 的 TypeIcon 映射

type 被解析为 loading 后,loading 消息会带有一个默认图标。这个图标在 PurePanel.tsx 中定义,五种类型的图标映射一目了然:

export const TypeIcon = {
  info: <InfoCircleFilled />,
  success: <CheckCircleFilled />,
  error: <CloseCircleFilled />,
  warning: <ExclamationCircleFilled />,
  loading: <LoadingOutlined />,
};

loading 类型对应 @ant-design/iconsLoadingOutlined,即一个持续旋转的加载图标。随后的 getMessageIcon(type, icon) 逻辑表明:若你在 config 中额外传入了 icon,将优先生效;否则才回落到 TypeIcon 中按类型匹配的默认图标。

getMessageIcon = (type?, icon?) => icon || (type && TypeIcon[type]) || null

LoadingOutlined 图标本身是静态的旋转图形(CSS 动画驱动),组件样式层通过 ant-message-notice-icon-loading 这类语义 class 保证其旋转动画效果。从代码结构可以推断,antd 刻意让 loading 消息的类型样式、图标与 info/success 等保持一致,只是替换了图标与容器语义 class,因此接入成本极低。

五、"异步自行移除"的几种落地方式

示例本身给出的方案是 setTimeout + destroy,但在真实业务中,销毁 loading 的时机应该与异步任务的完成时机绑定。以下从简单到复杂梳理几种可落地的写法。

5.1 方式一:setTimeout 延迟销毁(示例原版)

messageApi.open({ type: 'loading', content: 'Action in progress..', duration: 0 });
setTimeout(messageApi.destroy, 2500);

messageApi.destroy() 不带参数时销毁当前所有消息。示例用它模拟"异步任务 2.5 秒后完成"这一过程。

5.2 方式二:配合真实异步任务(async/await 或 Promise)

更贴合实际的模式是在异步操作完成或失败后主动销毁,同时结合 thenable 接口。示例文案中"异步自行移除"对应的本质是:loading 的出现是同步的,关闭是异步的。典型写法如下:

const doTask = async () => {
  messageApi.open({ type: 'loading', content: 'Saving...', duration: 0 });
  try {
    await saveData();          // 真实异步请求
    messageApi.success('Saved successfully');
  } catch {
    messageApi.error('Save failed');
  }
};

注意:这里的 messageApi.success 会在原 loading 仍存在时再叠加一条消息;若希望原地替换内容,可借助 key 定向更新(见 5.3)。

5.3 方式三:为每条 loading 分配 key,定向销毁

当同一时刻可能有多条 loading(如多个并发请求各自带一条"提交中"提示)时,推荐为每条消息显式传入 key,再用 destroy(key) 精确关闭。从 interface.tsMessageInstance 定义可以看到,destroy 的签名是 destroy: (key?: React.Key) => void——key 缺省时清空全部,传入 key 时只销毁指定那一条。

messageApi.open({ key: 'upload-1', type: 'loading', content: 'Uploading file A...', duration: 0 });
// 上传完成
messageApi.destroy('upload-1');

useMessage.tsx 的实现中,若调用 open 时没有传入 key,组件会自动生成形如 antd-message-${++keyIndex} 的自增唯一 key,保证每条消息可被独立管理。

5.4 thenable 接口:把关闭时机交给调用方

messageApi.open() 的返回值是 MessageType,在 interface.ts 中它被定义为 extends PromiseLike<boolean>,即同时具备"可调用"与"可 then"的能力。结合 wrapPromiseFn(定义于 util.ts),你可以这样手动关闭并在关闭后执行回调:

const closeFn = messageApi.open({ type: 'loading', content: 'Action in progress..', duration: 0 });
// 异步任务完成后手动关闭
closeFn?.then(() => {
  console.log('loading closed');
});
closeFn(); // 立即关闭

message 官方还提供 thenable 的独立示例(见 components/message/demo/thenable.tsx),它更详尽地演示了 Promise 形式的 messagelevel.then(afterClose) 用法。loading 场景下,这种形式比 setTimeout 更贴近真实的异步任务编排。

六、全局静态方法的 loading 实现:任务队列机制

前面示例使用的是 useMessage 的实例 API。若你在组件树之外(如工具函数、事件回调中)需要弹 loading,也可以使用全局静态方法 message.loading,API 文档给出的签名是:

  • message.loading(content, [duration], onClose)
  • message.loading(config)

静态方法在 index.tsx 中实现,其核心是一个"延迟渲染 + 任务队列"机制:

  1. 首次调用时创建 DocumentFragment,并在其中渲染 GlobalHolderWrapper,真正的挂载容器默认是 document.body
  2. 后续每次调用把 open / typeOpen / destroy 封装成 task 推入 taskQueue,再调用 flushMessageQueue() 按序执行;
  3. 由于静态方法内部持有的是与业务组件树隔离的 Context,源码中会调用 warnContext('message') 在非生产环境提示开发者:若消息内容依赖 Context,请改用 message.useMessage()App 组件

静态方法同样支持全局配置项,比如通过 message.config({ maxCount: 3 }) 限制最大同时展示条数(超出时丢弃最早一条)。如果你在多个模块中都需要弹出 loading 型提示,可以先统一 message.config 收敛行为,再在各个模块调用 message.loading

七、从测试用例看 loading 的预期行为

仓库的测试代码验证了 loading 消息的几类关键行为,可作为你使用时的行为契约参考:

  • 渲染成功demo.test.ts.snap 中存在 renders components/message/demo/loading.tsx correctly 的快照,确认按钮文案 Display a loading indicator 被正确渲染;
  • duration 为 0 时不会自动关闭index.test.tsxmessage.loading('Action in progress..', 0) 后,消息节点仍停留在 DOM 中;
  • destroy 可整体销毁:同文件中先通过两条 loading(均传 duration: 0)打开消息,随后调用 message.destroy() 全部移除;
  • 按 key 更新与定向关闭:测试中 message.loading({ content: 'Loading...', key }) 后再用同一 key 调用 message.success,实现从 loading 内容原地切换为完成结果,这正对应 5.3 节"key 驱动更新/销毁"的生产实践。

这些用例分布在 index.test.tsx,说明"不自动关闭 + 手动销毁 + key 管理"是 loading 消息经过测试保证的核心契约,你可以放心在业务中按同样模式使用。

八、小结:loading 消息的正确打开方式

总结本文要点,使用 ant-design Message 实现"全局 loading、异步自行移除"时,建议遵循以下模式:

  1. 优先用 message.useMessage() 获取 messageApicontextHolder,保证 Context 贯通与组件卸载清理;
  2. 务必设置 duration: 0,否则 loading 会在 3 秒(默认值)后自动消失,失去"加载中"的持续提示意义;
  3. 关闭时机与异步任务强绑定:任务完成时调用 messageApi.destroy()(全量)或 messageApi.destroy(key)(定向),也可以借助返回值的 thenable 特性编排销毁回调;
  4. 多任务并发时用 key 隔离,避免误删其他仍在进行的 loading 提示;
  5. 在组件树外部(如独立工具函数)使用时,可退回全局静态方法 message.loading,但需自行处理 Context 隔离问题。

示例源码 loading.tsx、接口定义 interface.ts 以及组件文档 index.en-US.md 是继续深入的最佳起点,源码中 duration 默认值与图标映射分别位于 useMessage.tsxPurePanel.tsx,值得逐一对照阅读。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388