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

React中Firebase存储下载URL无法存入Hook数组问题排查

React + Firebase Storage 上传多张图片后URL数组为空/useEffect单次输出单个URL的问题分析

问题描述

我在React中使用Firebase Storage,现有一个包含多张图片的数组,需将数组内每张图片上传至Firebase。获取下载URL后,希望存入useState Hook数组供后续使用,但数组始终为空。若用useEffect监听数组变化,每次仅输出单个URL而非完整URL数组,请问原因是什么?

const [uploadBilder, setUploadBilder] = useState([]);


await Promise.all(
mehrBilder?.map((bild) => {
  const storageRef = ref(
    storage,
    `${aktuellerUser._id}/artikelBilder/` + bild.name
  );
  const uploadTask = uploadBytesResumable(storageRef, bild);

  uploadTask.on(
    "state_changed",
    (snapshot) => {
      const progress =
        (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
      console.log("Upload is " + progress + "% done");
      switch (snapshot.state) {
        case "paused":
          console.log("Upload is paused");
          break;
        case "running":
          console.log("Upload is running");
          break;
      }
    },
    (error) => {
      console.log(error);
    },
    () => {
      getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
        setUploadBilder((prev) => [...prev, downloadURL]);
        console.log(uploadBilder); //EMPTY
      });
    }
  );
})
);

useEffect(() => {
 console.log(uploadBilder) //count 3 = right!
},[uploadBilder])

核心原因

  1. useState更新的异步特性:
    React的state更新是异步批量处理的,你在setUploadBilder后直接打印uploadBilder,拿到的是当前渲染周期的旧state值,自然为空。只有在下一次渲染时,state才会更新为新值。

  2. Promise.all未正确等待上传完成:
    你的map回调没有返回Promise对象,uploadTask.on是基于回调的API,Promise.all实际上不会等待所有上传任务完成就提前执行结束。每个图片上传完成的时间不确定,会各自触发setUploadBilder,导致useEffect在每次state更新时单独触发,每次输出新增的单个URL。

修复方案

将每个上传任务封装为Promise,让Promise.all能正确等待所有上传完成,再一次性将所有URL存入state:

const [uploadBilder, setUploadBilder] = useState([]);

const uploadImages = async () => {
  try {
    // 把每个上传逻辑封装为Promise
    const uploadPromises = mehrBilder?.map((bild) => {
      return new Promise((resolve, reject) => {
        const storageRef = ref(
          storage,
          `${aktuellerUser._id}/artikelBilder/${bild.name}`
        );
        const uploadTask = uploadBytesResumable(storageRef, bild);

        uploadTask.on(
          "state_changed",
          (snapshot) => {
            const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
            console.log(`Upload is ${progress}% done`);
            switch (snapshot.state) {
              case "paused":
                console.log("Upload is paused");
                break;
              case "running":
                console.log("Upload is running");
                break;
            }
          },
          (error) => {
            console.error(error);
            reject(error); // 上传失败时抛出错误
          },
          async () => {
            try {
              const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
              resolve(downloadURL); // 上传成功后返回URL
            } catch (urlError) {
              console.error(urlError);
              reject(urlError);
            }
          }
        );
      });
    });

    // 等待所有上传完成,获取完整URL数组
    const allDownloadUrls = await Promise.all(uploadPromises);
    // 一次性更新state
    setUploadBilder(allDownloadUrls);
    console.log(allDownloadUrls); // 此处可直接拿到完整URL数组
  } catch (err) {
    console.error("批量上传失败:", err);
  }
};

// 在合适时机调用上传函数(比如按钮点击、组件挂载时,注意控制依赖)
// useEffect(() => {
//   if (mehrBilder && aktuellerUser) {
//     uploadImages();
//   }
// }, [mehrBilder, aktuellerUser]);

useEffect(() => {
  console.log(uploadBilder); // 现在会在所有URL上传完成后,输出完整数组
}, [uploadBilder]);

额外说明

  • 封装Promise后,Promise.all会等待所有上传任务完成再返回结果,此时一次性更新state,useEffect只会触发一次,直接输出完整URL数组。
  • 避免了多次调用setUploadBilder导致的state频繁更新,同时解决了直接打印旧state的问题。

内容的提问来源于stack exchange,提问作者Captai-N

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 03:00:53