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])
核心原因
useState更新的异步特性:
React的state更新是异步批量处理的,你在setUploadBilder后直接打印uploadBilder,拿到的是当前渲染周期的旧state值,自然为空。只有在下一次渲染时,state才会更新为新值。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
相关产品推荐
相关产品推荐

