antd Progress 动态进度条实战:用 React 状态驱动可交互的进度展示
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/gh_mirrors/ant/ant-design
导读
本文以 Ant Design(antd)Progress组件的dynamic官方示例为核心,讲解如何用 ReactuseState状态驱动进度条实时变化,完整覆盖线型(line)与圆型(circle)两种形态的用法。文中不仅给出可直接运行的完整代码,还结合本仓库中 Progress 源码、Line 实现、Circle 实现 及 demo 快照测试 剖析底层渲染与边界钳制原理,帮助你从"会用"进阶到"懂原理",并能将其迁移到上传进度、任务执行、表单提交等真实业务场景。
示例背景:官方 "Dynamic" Demo 是什么
在 antd 官方文档中,Progress 组件提供了约 16 个示例(见 Progress 组件文档),其中 "Dynamic"(动态展示)示例对应的正是本文要讲解的 dynamic.tsx 及其 dynamic.md 说明。官方对该示例的中文描述只有一句话:
会动的进度条才是好进度条。
英文为 "A dynamic progress bar is better."——这句话点明了示例的核心意图:Progress 并非只能静态展示一个固定百分比,而是可以随着用户交互或数据变化实时刷新。该示例通过"加号 / 减号"两个按钮控制进度值,让线型进度条与圆型进度圈同步变化,是理解 antd 受控组件用法的经典入门案例。
从组件目录结构看,该示例被index.en-US.md与index.zh-CN.md通过<code src="./demo/dynamic.tsx">的方式挂载到文档示例区,并会被 demo 测试快照(renders components/progress/demo/dynamic.tsx correctly)自动渲染校验,保证示例代码始终可用。
完整代码与逐行拆解
示例的完整代码位于 components/progress/demo/dynamic.tsx,核心实现如下:
import React, { useState } from 'react'; import { MinusOutlined, PlusOutlined } from '@ant-design/icons'; import { Button, Flex, Progress } from 'antd'; const App: React.FC = () => { const [percent, setPercent] = useState<number>(0); const increase = () => { setPercent((prevPercent) => { const newPercent = prevPercent + 10; if (newPercent > 100) { return 100; } return newPercent; }); }; const decline = () => { setPercent((prevPercent) => { const newPercent = prevPercent - 10; if (newPercent < 0) { return 0; } return newPercent; }); }; return ( <Flex vertical gap="small"> <Flex vertical gap="small"> <Progress percent={percent} type="line" /> <Progress percent={percent} type="circle" /> </Flex> <Button.Group> <Button onClick={decline} icon={<MinusOutlined />} /> <Button onClick={increase} icon={<PlusOutlined />} /> </Button.Group> </Flex> ); }; export default App;1. 状态管理:唯一数据源
const [percent, setPercent] = useState<number>(0);进度值percent是页面上唯一的"数据源",两个 Progress 组件共享同一个状态,因此点击按钮后线型与圆型进度条会保持数值一致、同步动画。初始值为0,对应 Progress 组件percent属性的默认值(见 progress.tsx 源码 中percent = 0的默认解构)。
2. 步进函数:基于函数式更新的加减逻辑
const increase = () => { setPercent((prevPercent) => { const newPercent = prevPercent + 10; if (newPercent > 100) { return 100; } return newPercent; }); }; const decline = () => { setPercent((prevPercent) => { const newPercent = prevPercent - 10; if (newPercent < 0) { return 0; } return newPercent; }); };这里有两个值得注意的编码细节:
- 函数式更新(
setPercent(prev => ...)):回调接收上一次的状态值,而非直接读取闭包中的percent。这在连续多次调用、或进度由定时器/异步任务驱动时能避免闭包过期问题,是 React 官方推荐的写法。 - 边界钳制(clamp):每次加 10,超过 100 就固定为 100;每次减 10,小于 0 就固定为 0。这保证了传入
percent的值永远在合法区间内。实际上即使你忘了做这个判断,组件内部也有兜底——见下文"源码级原理"中对validProgress的分析。
3. 布局与按钮组
<Flex vertical gap="small"> ... <Button.Group> <Button onClick={decline} icon={<MinusOutlined />} /> <Button onClick={increase} icon={<PlusOutlined />} /> </Button.Group> </Flex>Flex是 antd 的弹性布局组件,vertical让子元素纵向排列,gap="small"控制间距;Button.Group将两个图标按钮组合成连体按钮组;- 按钮通过
icon属性引入@ant-design/icons的MinusOutlined(减号)与PlusOutlined(加号),点击分别触发decline与increase。
两种形态:line 与 circle 的渲染差异
示例同时展示了 Progress 最常用的两种形态:
<Progress percent={percent} type="line" /> <Progress percent={percent} type="circle" />| 形态 | type取值 | 渲染方式 | 默认尺寸 |
|---|---|---|---|
| 线型进度条 | line(默认值) | div+ CSS 宽度百分比(见 Line.tsx) | 高度 8px(small为 6px) |
| 圆型进度圈 | circle | 基于rc-progress的 SVG<circle>(见 Circle.tsx) | 120 × 120(small为 60) |
从 utils.ts 的getSize实现 可以确认:
- 线型默认高度:
size === 'small' ? 6 : 8,且宽度为-1时外层自动撑满100%; - 圆型默认尺寸:
size === 'small' ? 60 : 120,内部文字字号由width * 0.15 + 6计算得出(见 Circle.tsx),所以圆越大,中央百分比数字越大。
源码级原理:percent 是如何变成进度条的
理解动态进度的底层逻辑,需要看 Progress 的渲染链路:progress.tsx(入口分发)→Line.tsx/Circle.tsx(具体形态)→utils.ts(数值校验)。
1. 数值钳制:validProgress
在 utils.ts 中:
export function validProgress(progress?: number) { if (!progress || progress < 0) { return 0; } if (progress > 100) { return 100; } return progress; }无论你传入的percent是负数、NaN还是大于 100 的值,最终渲染前都会被钳制到 0~100 区间。线型进度条的宽度正是${validProgress(percent)}%(见 Line.tsx),圆型进度圈则通过getPercentage计算出实际角度数组(见 utils.ts)。这与示例中手动做边界判断的思路一致,属于"双保险"。
2. 状态自动判定:percent ≥ 100 即 success
在 progress.tsx 中:
const progressStatus = React.useMemo(() => { if (!ProgressStatuses.includes(status!) && percentNumber >= 100) { return 'success'; } return status || 'normal'; }, [status, percentNumber]);当未显式指定status且进度达到 100 时,组件会自动进入success状态:线型进度条变为绿色,圆型进度圈中央的数字会替换为对勾图标(CheckCircleFilled/CheckOutlined,见 progress.tsx)。也就是说,在 dynamic 示例中把进度加到 100 时,你会看到状态自动变化——无需额外编码。
3. 无障碍与可访问性:role="progressbar"
从 demo 快照 可以看到,Progress 渲染出的 DOM 自带完整 ARIA 语义:
<div aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" class="ant-progress ant-progress-status-normal ant-progress-line ..." role="progressbar" >这来自 progress.tsx 中对容器元素role="progressbar"、aria-valuenow={percentNumber}、aria-valuemin={0}、aria-valuemax={100}的设置。percent每次变化,aria-valuenow都会随之更新,屏幕阅读器用户也能感知进度变化,这是动态进度在无障碍层面的关键保障。
4. 动画过渡
圆型进度圈的 SVG 路径带有transition: stroke-dashoffset .3s ease ...(见快照中style属性),线型进度条同样依赖 CSS 过渡。因此当percent状态被按钮快速连续修改时,进度条是平滑"流动"的,而不是生硬跳变——这正是"会动的进度条"体验的来源。
扩展实战:把动态进度迁移到真实场景
掌握了状态驱动的思路后,你可以轻松扩展出多种真实场景的进度方案。
场景一:定时器驱动的进度(如倒计时、任务轮询)
import React, { useEffect, useState } from 'react'; import { Progress } from 'antd'; const AutoProgress: React.FC = () => { const [percent, setPercent] = useState(0); useEffect(() => { const timer = setInterval(() => { setPercent((prev) => (prev >= 100 ? 0 : prev + 1)); }, 100); return () => clearInterval(timer); }, []); return <Progress percent={percent} />; };注意这里依然使用函数式更新setPercent((prev) => ...),避免定时器闭包读到过期状态。
场景二:自定义文字格式(format)
进度文案并不一定是百分比。参考 format 示例,可以这样改造:
<Progress percent={percent} format={(p) => (p >= 100 ? 'Done' : `${p} Days`)} />format的函数签名是(percent?: number, successPercent?: number) => ReactNode,默认值为(percent) => percent + '%'(见 index.en-US.md API 表格)。
场景三:更多参数组合
结合 Progress API 文档 与 ProgressProps 类型定义,常用的动态场景参数如下:
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
percent | 完成百分比,动态进度的核心驱动值 | number | 0 |
type | 形态:line/circle/dashboard | string | line |
showInfo | 是否显示进度数值与状态图标 | boolean | true |
status | 状态:success/exception/normal/active(仅 line) | string | - |
strokeColor | 进度条颜色,传对象可渲染渐变 | string | string[] | object | - |
trailColor | 未填充部分的颜色 | string | - |
strokeLinecap | 端点样式:round/butt/square | string | round |
size | 尺寸:数字 / 数组 / 对象 /small/default | number | [number, number] | object | string | default |
success | 成功段配置:{ percent, strokeColor } | object | - |
steps | 分步显示总数(line 为 number,circle 可为对象) | number | { count, gap } | - |
例如模拟一个带失败状态的上传进度:
<Progress percent={percent} status={percent === 100 ? 'success' : 'active'} strokeColor={{ from: '#108ee9', to: '#87d068' }} />status="active"会为线型进度条附加流动动画条纹;strokeColor传入{ from, to }对象时,底层 handleGradient 会生成linear-gradient(to right, from, to)渐变背景。注意status是受控的:一旦显式传入,组件就不再依据percent >= 100自动切换状态。
小结
通过官方dynamic示例,我们掌握了 antd Progress 动态进度的完整套路:一个状态值 + 函数式更新 + 边界钳制 + 受控渲染。其底层由 progress.tsx 统一分发、Line.tsx 与 Circle.tsx 分别渲染、utils.ts 保证数值合法,同时内置了 ARIA 无障碍语义与平滑过渡动画。把这套模式套用到上传、任务、轮询等任何"过程可量化"的业务中,即可快速产出体验良好的进度反馈。
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/gh_mirrors/ant/ant-design
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考