React+Firebase文件上传时setCount无法递增的问题排查
问题描述
开发了一个小型文件上传应用,希望获取每个文件上传完成的进度。但在使用Firebase Storage上传文件时,setCount并未在每个文件上传时递增,仅在全部上传完成后才增加1。
用户代码如下:
import { ref, uploadBytesResumable } from "firebase/storage" import { proStorage } from "../firebase/config" import { v4 } from "uuid" import { useState } from "react" const useUploader = (data, foldername) => { const [upLoadcomplete, setuploacomplete] = useState("") const [count, setCount] = useState(0) const uploadFiles = async () => { for (let i = 0; i < data.length; i++) { const name = `$${v4()}` const location = `${foldername}/${name}` const metadata = { customMetadata: { uuid_name: name, }, } const dataRef = ref(proStorage, location) const uploadtask = uploadBytesResumable(dataRef, data[i], metadata) uploadtask.on( "state_changed", (snapshot) => { const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100 console.log("Upload is " + progress + "% done") // eslint-disable-next-line switch (snapshot.state) { case "paused": console.log("Upload is paused") break case "running": console.log("Upload is running") setCount( count + 1 ) break } setuploacomplete("complete") }, (error) => { setuploacomplete("Error with upload try again") } ) } console.log(upLoadcomplete) } return [uploadFiles, upLoadcomplete] } export default useUploader
问题原因
- 闭包捕获旧状态值:
for循环中创建的所有上传监听回调,都捕获了初始化时的count变量(值为0)。由于React状态更新是异步的,闭包会保留变量的旧引用,导致所有回调执行setCount(count + 1)时,实际都是0 + 1 = 1,最终count只会变成1。 - 状态更新时机错误:将
setCount放在"running"分支是错误的——该状态会在上传进度更新时被多次触发,会导致count被重复递增;而需求是每个文件上传完成后递增count,应该监听上传完成的事件。 - 异步循环未等待上传完成:
uploadFiles标记为async,但内部for循环没有等待单个上传任务完成,瞬间创建所有上传任务,导致状态更新逻辑混乱。
修复方案
修改代码解决闭包问题,并调整状态更新时机:
import { ref, uploadBytesResumable } from "firebase/storage" import { proStorage } from "../firebase/config" import { v4 } from "uuid" import { useState } from "react" const useUploader = (data, foldername) => { const [upLoadcomplete, setuploacomplete] = useState("") const [count, setCount] = useState(0) // 用函数式更新获取最新状态,避免闭包问题 const incrementCount = () => setCount(prev => prev + 1) const uploadFiles = async () => { // 重置初始状态 setCount(0) setuploacomplete("") for (let i = 0; i < data.length; i++) { const name = `$${v4()}` const location = `${foldername}/${name}` const metadata = { customMetadata: { uuid_name: name, }, } const dataRef = ref(proStorage, location) const uploadtask = uploadBytesResumable(dataRef, data[i], metadata) // 用Promise包裹上传任务,让for循环等待单个上传完成 await new Promise((resolve, reject) => { uploadtask.on( "state_changed", (snapshot) => { const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100 console.log(`Upload ${i+1} is ${progress}% done`) switch (snapshot.state) { case "paused": console.log("Upload is paused") break case "running": console.log("Upload is running") break } }, (error) => { setuploacomplete("Error with upload try again") reject(error) }, // 上传完成时触发回调,更新count () => { console.log(`Upload ${i+1} completed`) incrementCount() resolve() } ) }) } setuploacomplete("All uploads complete") console.log(upLoadcomplete) } return [uploadFiles, upLoadcomplete, count] // 返回count供外部使用 } export default useUploader
关键修改点
- 函数式状态更新:使用
setCount(prev => prev + 1),确保每次获取最新的状态值,解决闭包捕获旧值的问题。 - 等待单个上传完成:用Promise包裹上传任务,让for循环依次执行,避免所有上传任务同时触发状态更新。
- 调整更新时机:将count递增逻辑放在上传完成的回调(
uploadtask.on的第三个参数)中,确保每个文件上传完成后仅递增一次。 - 重置初始状态:上传开始时重置count和上传状态,避免多次上传时状态混乱。
内容的提问来源于stack exchange,提问作者Brian
相关产品推荐
相关产品推荐

