React中设置下载进度状态:进度无法逐文件更新的问题
React中批量下载文件时进度条实时更新问题修复
修复后的代码
const [progress, setProgress] = useState(0); const [loading, setLoading] = useState(false); const [checkedPlayers, setCheckedPlayers] = useState([]); // 用useRef保存计数,避免闭包捕获陈旧值 const countRef = useRef(0); const handleDownload = () => { console.log("handleDownload"); setLoading(true); countRef.current = 0; // 重置下载计数 if (selectedPlayers.length > 0) { const hr = 3600; const signedUrlExpireSeconds = hr * 1; const s3 = new S3Client({ region: region, signatureVersion: 'v4', credentials: { accessKeyId: accessKeyId, secretAccessKey: secretAccessKey } }); const downloadPromises = selectedPlayers.map(async player => { const myKey = player.Key; const bucketParams = { Bucket: bucket, Key: myKey, }; try { const url = await getSignedUrl(s3, new GetObjectCommand(bucketParams), { expiresIn: signedUrlExpireSeconds }); console.log(`Signed URL for player ${player.Key}: ${url}`); // 等待文件下载完成后再更新进度 await new Promise((resolve, reject) => { new jsDownloader({ url: url, onSuccess: () => resolve(), onError: (err) => reject(err) }); }); // 更新计数和进度 countRef.current++; setProgress(Math.round(countRef.current / selectedPlayers.length * 100)); } catch (error) { console.log(error); // 即使下载失败也更新进度(可根据业务需求调整是否跳过) countRef.current++; setProgress(Math.round(countRef.current / selectedPlayers.length * 100)); } }); Promise.all(downloadPromises) .then(() => { setLoading(false); setCheckedPlayers([]); setProgress(0); }) .catch(error => console.log(error)); } };
问题根源与修复说明
原问题原因
- 闭包导致的计数陈旧:原代码中
count是普通变量,在map的异步函数里,每次调用setProgress时捕获的都是初始的count值,React会批量处理多次setProgress调用,最终只显示最后一次计算结果,导致进度无法实时更新。 - 进度更新时机错误:原代码在拿到S3签名URL后就更新进度,但此时文件还未开始下载,进度更新不匹配实际下载状态。
核心修复点
- 用useRef存储计数:
useRef的current属性是可变的,不会被闭包捕获旧值,确保每次更新进度时都能获取到最新的已完成下载数。 - 等待下载完成再更新进度:通过Promise包装
jsDownloader,利用其成功/失败回调,确保在文件实际下载完成(或失败)后再更新进度。 - 错误场景处理:单个文件下载失败时也更新进度,避免进度停滞,可根据业务需求调整是否跳过失败文件的计数。
内容的提问来源于stack exchange,提问作者sbaden
相关产品推荐
相关产品推荐

