You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextJS项目中React文件上传进度条组件未按预期频繁更新问题

问题根因

React 对 state 采用浅比较策略,你在 progressCallback 中直接修改了原有 files 数组内的对象属性,再将原数组传入 setFiles,由于数组引用没有发生变化,React 判定 state 未更新,因此不会触发组件重渲染,这也是控制台能正常打印进度但视图无变化的核心原因。

除此之外还存在两个会导致异常的潜在问题:

  • 嵌套在组件内的 Thumbs 函数会随父组件每次渲染重新创建,容易引发不必要的组件卸载重挂载
  • useEffect 依赖 files 数组,每次更新进度修改 files 都会重新触发上传逻辑,导致同一份文件被多次上传

修复方案

1. 修正进度回调的state更新逻辑

采用函数式更新获取最新的 state,创建新的数组和文件对象副本,保证引用发生变化触发重渲染,同时避免闭包捕获旧的 files 数组导致的异常:

const progressCallback = useCallback((progressEvent, file) => {
  const progress: number = Math.ceil((progressEvent.loaded as number * 100) / progressEvent.total);
  console.log(progress);
  setFiles(prevFiles => prevFiles.map(f => 
    f.name === file.name ? {...f, progress} : f
  ));
}, []);

2. 修复重复上传问题

新增 ref 标记上传状态,避免 useEffect 重复触发上传逻辑:

const hasUploadStarted = useRef(false);

useEffect(() => {
  if(!files?.length || hasUploadStarted.current) return;
  hasUploadStarted.current = true;
  setUploading(true);
  files.forEach((file) => {
    CenterService.uploadFileToCenter(center._id, file, progressCallback).then(response => {
      const uploadedFile = response?.data?.data?.url;
      if (uploadedFile) {
        const tempFiles = center?.files ? [...center.files, uploadedFile] : [uploadedFile];
        formik.setFieldValue('files', uniq(tempFiles));
      }
      setUploading(false);
    });
  });
}, [center._id, formik, progressCallback]);

3. 优化Thumbs组件定义

将 Thumbs 组件移到父组件外部,通过 props 传递所需参数,避免重复创建组件:

// 放在父组件外部定义
const Thumbs = ({ files, uploading, removeImage }) => {
  return <div className="uploaded-files-container"> 
    {files.map(file => (
      <div className="uploaded-file-container" key={file.name}>
        <div className="thumbnail-container">
          <div className="thumbnail" style={{backgroundImage: `url(${file.preview || '/_next/static/media/bg-searchTreatment.aa8648dea68731269b666fbe58cd505f.jpg'})`}}></div>
        </div>
        <div className="details-container">
          <div className="progress-indicator" style={{width: `${file.progress || 0}%`}}></div>
          { file.progress && <div className="progress-number">{ file.progress }%</div> }
          <div className="file-name">{file.name}</div>
          <div className="file-size">{(file.size/1024).toFixed(0)}KB</div>
        </div>
        <div className="actions-container">
          { file.progress && file.progress < 100 && <Spinner /> }
          { !uploading && <a onClick={() => removeImage(file)}><Image src="/assets/remove.svg" height="30" width="30" /></a> }
        </div>
      </div>
    ))}
  </div>
};

// 父组件内调用
<Thumbs files={files} uploading={uploading} removeImage={removeImage} />

内容的提问来源于stack exchange,提问作者Multitut

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 13:06:04