React上传图片至Firebase时Firestore重复生成2条相同文档问题
React照片图库上传单图时Firestore生成2条重复文档问题排查
问题表现
- 基于React开发的照片图库应用,上传单张图片时,Firestore中会生成2条对应同一张图片的文档
- 两条重复文档的
createdAt、url字段完全一致,但文档ID互不相同 - 预期行为:上传单张图片仅在Firestore中生成1条对应文档,暂未定位到代码重复执行、触发重复上传的根因
相关实现代码
图片上传逻辑:自定义Hook useStorage
import { useState, useEffect } from 'react' import { projectStorage, projectFirestore, timestamp } from '../firebase/config' const useStorage = (file) => { const [progress, setProgress] = useState(0) const [error, setError] = useState(null) const [url, setUrl] = useState(null) useEffect(() => { // references const storageRef = projectStorage.ref(file.name) const collectionRef = projectFirestore.collection('images') // uploading the file to the reference storageRef.put(file).on( 'state_changed', (snap) => { let percentage = (snap.bytesTransferred / snap.totalBytes) * 100 setProgress(percentage) }, (err) => { setError(err) }, async () => { const url = await storageRef.getDownloadURL() const createdAt = timestamp() await collectionRef.add({ url, createdAt }) setUrl(url) } ) }, [file]) return { progress, url, error } } export default useStorage
数据查询逻辑:自定义Hook useFirestore
import { useEffect, useState } from 'react' import { projectFirestore } from '../firebase/config' const useFirestore = (collection) => { const [docs, setDocs] = useState([]) useEffect(() => { // return a function to un-subscribe from the collection const unsub = projectFirestore .collection(collection) .orderBy('createdAt', 'desc') .onSnapshot((snap) => { let documents = [] snap.forEach((doc) => { documents.push({ ...doc.data(), id: doc.id }) }) setDocs(documents) }) // clean-up function return () => unsub() }, [collection]) return { docs } } export default useFirestore
问题相关截图


根因分析
重复写入的核心原因是React 18开发环境严格模式的双重副作用检测机制+上传逻辑缺少清理/去重判断:
- React 18版本中,开启严格模式(默认开启)后,开发环境会对挂载阶段的
useEffect执行「执行→清理→再执行」的双重调用,用来提前暴露副作用缺少清理逻辑的隐患,生产环境构建后不会触发该机制。 - 当前
useStorage中的上传副作用没有返回清理函数,也没有做重复上传拦截:第一次effect触发的上传任务还未执行到Firestore写入步骤,第二次effect已经触发了完全相同的第二次上传流程,两个上传任务最终拿到相同的文件下载地址、生成相同的时间戳,先后写入Firestore就形成了两条字段一致、ID不同的重复文档。 useFirestore逻辑本身已经正确实现了快照监听的清理,不存在重复查询问题。
修复方案
给useStorage的effect增加取消标记和清理逻辑,拦截重复的写入操作,修复后代码如下:
import { useState, useEffect } from 'react' import { projectStorage, projectFirestore, timestamp } from '../firebase/config' const useStorage = (file) => { const [progress, setProgress] = useState(0) const [error, setError] = useState(null) const [url, setUrl] = useState(null) useEffect(() => { // 新增取消标记,拦截失效effect的后续操作 let isCancelled = false const storageRef = projectStorage.ref(file.name) const collectionRef = projectFirestore.collection('images') // 保存上传任务实例,用于需要时取消上传 const uploadTask = storageRef.put(file) uploadTask.on( 'state_changed', (snap) => { if (!isCancelled) { const percentage = (snap.bytesTransferred / snap.totalBytes) * 100 setProgress(percentage) } }, (err) => { if (!isCancelled) setError(err) }, async () => { const fileUrl = await storageRef.getDownloadURL() const createdAt = timestamp() // 仅当当前effect有效时才执行写入和状态更新 if (!isCancelled) { await collectionRef.add({ url: fileUrl, createdAt }) setUrl(fileUrl) } } ) // 清理函数:effect重执行/组件卸载时触发,标记当前任务失效 return () => { isCancelled = true // 可选:调用cancel()直接终止正在进行的上传任务,节省带宽 uploadTask.cancel() } }, [file]) return { progress, url, error } } export default useStorage
修复后第一次effect触发的上传任务会在第二次effect执行时被标记为取消,即使后续拿到了文件地址也不会执行Firestore写入操作,从根源避免重复文档生成。
内容的提问来源于stack exchange,提问作者sky_walker18
相关产品推荐
相关产品推荐

