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

如何结合React useState与uploadBytesResumable跟踪多文件上传进度

解决Firebase Storage多文件异步上传的React状态更新问题

核心问题原因

  1. 闭包捕获旧状态:异步上传的回调函数会捕获循环中的旧filesStatus引用,导致状态更新基于过时的值。
  2. 文件名冲突:如果上传同名文件,状态中的键会被覆盖,无法区分不同文件的上传进度。

解决方案代码

import { v4 as uuidv4 } from 'uuid';
import { ref, uploadBytesResumable, UploadTaskSnapshot } from 'firebase/storage';
import React from 'react';

type FilesStatus = {
  [uuid: string]: UploadTaskSnapshot;
};

export const useFileUpload = () => {
  const [filesStatus, setFilesStatus] = React.useState<FilesStatus>({});

  const uploadFiles = (files: File[]) => {
    files.forEach((file) => {
      // 为每个文件生成唯一ID,避免同名文件状态覆盖
      const fileId = uuidv4();
      const storageRef = ref(storage, `files/${file.name}`);
      const uploadTask = uploadBytesResumable(storageRef, file, { contentType: file.type });

      uploadTask.on(
        'state_changed',
        (snapshot) => {
          // 使用函数式更新,确保基于最新状态修改
          setFilesStatus((prevStatus) => ({
            ...prevStatus,
            [fileId]: snapshot,
          }));
        },
        (error) => {
          console.error(`文件 ${file.name} 上传失败:`, error);
          // 可选:扩展状态存储错误信息,需调整FilesStatus类型
          // setFilesStatus(prev => ({
          //   ...prev,
          //   [fileId]: { ...prev[fileId], error }
          // }));
        },
        () => {
          console.log(`文件 ${file.name} 上传完成`);
          // 可选:获取文件下载URL
          // getDownloadURL(uploadTask.snapshot.ref).then(url => {
          //   console.log('下载链接:', url);
          // });
        }
      );
    });
  };

  return { filesStatus, uploadFiles };
};

关键优化点

  • 唯一ID标识:通过uuid生成独立的文件ID,彻底避免同名文件的状态键冲突。
  • 函数式状态更新:使用setFilesStatus(prevStatus => {...}),每次更新都基于最新的状态快照,解决闭包导致的旧状态引用问题。
  • 独立回调作用域:用forEach遍历文件,每个文件的回调函数绑定当前迭代的file和fileId,避免循环变量共享导致的混淆。

UI使用示例

import { useFileUpload } from './useFileUpload';

const UploadComponent = () => {
  const { filesStatus, uploadFiles } = useFileUpload();

  const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
    const selectedFiles = e.target.files;
    if (selectedFiles) uploadFiles(Array.from(selectedFiles));
  };

  return (
    <div>
      <input type="file" multiple onChange={handleFileSelect} />
      <div className="upload-progress">
        {Object.entries(filesStatus).map(([fileId, snapshot]) => (
          <div key={fileId} className="file-item">
            <h4>{snapshot.metadata.name}</h4>
            <progress 
              value={(snapshot.bytesTransferred / snapshot.totalBytes) * 100} 
              max="100"
            />
            <p>进度:{Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100)}%</p>
            <p>状态:{snapshot.state}</p>
          </div>
        ))}
      </div>
    </div>
  );
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 12:53:11