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

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

问题原因

  1. 闭包捕获旧状态值:for循环中创建的所有上传监听回调,都捕获了初始化时的count变量(值为0)。由于React状态更新是异步的,闭包会保留变量的旧引用,导致所有回调执行setCount(count + 1)时,实际都是0 + 1 = 1,最终count只会变成1。
  2. 状态更新时机错误:将setCount放在"running"分支是错误的——该状态会在上传进度更新时被多次触发,会导致count被重复递增;而需求是每个文件上传完成后递增count,应该监听上传完成的事件。
  3. 异步循环未等待上传完成: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 21:44:50