React调用setState更新文件状态后,上传函数内获取状态为空如何解决?
问题原因
你遇到的是React Hooks的闭包陷阱,再加上React状态更新是异步批处理的,两个原因叠加导致的问题:
- 你在
handleFileUpload里调用setFiles后,状态不会立刻更新,此时直接调用uploadFile,函数捕获的还是当前闭包里的旧files值(也就是初始的空数组) uploadFile及其内部的onprogress回调都被绑定了触发时的旧闭包,之后即使files状态更新,这两个函数里拿到的还是初始的旧值
解决方案
最简单的方案是使用setState的函数式更新语法,同时补全缺失的参数:
步骤1:修改handleFileUpload,传入新增文件的索引
你需要把新上传的文件在files数组里对应的索引传给uploadFile,这样后续更新进度时才能找到对应的文件项:
const [files,setFiles] = useState([]); const handleFileUpload = async (e) => { const fileList = Array.from(e.target.files); const filesStructure = fileList.map((file: any) => ({ progress: 0, file, })); const clonedFiles = [...files, ...filesStructure]; setFiles(clonedFiles); filesStructure.forEach((fileItem, idx) => { // 计算新文件在最终数组里的索引:原有文件长度 + 当前遍历下标 const newFileIndex = files.length + idx; uploadFile(fileItem.file, post.id, newFileIndex); }); e.target.value = ""; };
步骤2:修改uploadFile,用函数式更新进度
setFiles传入回调函数时,回调的第一个参数永远是最新的状态值,不受闭包旧值的影响:
const uploadFile = async (file, postId: string, index: number) => { setIsLoading(true); const formData = new FormData(); formData.append("file", file); formData.append("post_id", postId); try { const onprogress = (event) => { const progress = Math.round((event.loaded * 100) / event.total); // 函数式更新,直接拿最新的files状态 setFiles(prevFiles => { const clonedFiles = [...prevFiles]; // 不可变更新,不要直接修改原对象 clonedFiles[index] = { ...clonedFiles[index], progress }; return clonedFiles; }); }; const res = await uploadFileService(formData, onprogress); } catch (error) { console.log(error.message); } finally { setIsLoading(false); } };
可选扩展:如果确实需要在uploadFile里直接读取最新的files
可以用useRef存储files的快照,每次状态更新时同步更新ref:
const [files, setFiles] = useState([]); const filesRef = useRef(files); // 监听files变化,同步更新ref useEffect(() => { filesRef.current = files; }, [files]);
之后在uploadFile里访问filesRef.current就是最新的files值。
内容的提问来源于stack exchange,提问作者Berserk Guts
相关产品推荐
相关产品推荐

