如何结合React useState与uploadBytesResumable跟踪多文件上传进度
解决Firebase Storage多文件异步上传的React状态更新问题
核心问题原因
- 闭包捕获旧状态:异步上传的回调函数会捕获循环中的旧
filesStatus引用,导致状态更新基于过时的值。 - 文件名冲突:如果上传同名文件,状态中的键会被覆盖,无法区分不同文件的上传进度。
解决方案代码
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
相关产品推荐
相关产品推荐

