React Hooks:在组件内部处理异步 setState 的正确方法是什么? (上传进度axios)
React Hooks: what is the correct way to handle async setState inside component ? (uploadProgress axios)
我们想post并行处理多个文件并显示上传进度。
我们第一次尝试解决它是在 uploadProgress 回调中使用 setState 和之前的状态:
const FileUpload = () => {
const [pendingUploads, setPendingUploads] = useState({});
// For clarity: files = Record<string, {id: string, progress: string, data: FormData}>
const handleChange = files => {
setPendingUploads(files);
const handleUploadProgress = (progressEvent, id) => {
const percentCompleted = (progressEvent.loaded * 100) / progressEvent.total;
// Problem: pendingUploads is always empty, we do not get the updated value from last update
setPendingUploads({
...pendingUploads,
[id]: {
...pendingUploads[id],
progress: `${percentCompleted}%`
}
});
};
files.forEach(file => {
axios.post("/files", file.data, {
onUploadProgress: e => handleUploadProgress(e, file.id)
});
});
};
return (
<>
<div>
{_.map(pendingUpload, (pendingUpload, idx) => (
<div key={idx}>
{pendingUpload.id}: {pendingUpload.progress}
</div>
))}
</div>
<input multiple type="file" onChange={handleChange} />
</>
);
};
pendingUploads 始终为空,因此一次仅显示 1 个文件的进度值。
正确的做法是什么?到目前为止,我们有 2 个工作示例:
1) 解决方案 1
- 在状态 (pendingUploads) 中保留待上传地图
- 在 handleUploadProgress(axios 回调)中使用 setState 触发进度更新(progressUpdate)
- 当 progressUpdate 改变时更新 useEffect 中的 pendingUploads
const FileUpload = () => {
const [pendingUploads, setPendingUploads] = useState({});
const [progressUpdate, setProgressUpdate] = useState({ id: "", value: "" });
useEffect(() => {
setPendingUploads({
...pendingUploads,
[progressUpdate.id]: {
...pendingUploads[progressUpdate.id],
progress: progressUpdate.value
}
});
}, [progressUpdate]);
const handleUploadProgress = (progressEvent, id) => {
const percentCompleted = (progressEvent.loaded * 100) / progressEvent.total;
setProgressUpdate({ id, value: `${percentCompleted}%` });
};
// files = Record<string, {id: string, progress: string, data: FormData}>
const handleChange = files => {
setPendingUploads(files);
files.forEach(file => {
axios.post("/files", file.data, {
onUploadProgress: e => handleUploadProgress(e, file.id)
});
});
};
return (
<div>
<div>
{_.map(pendingUploads, (pendingUpload, idx) => (
<div key={idx}>
{pendingUpload.id}: {pendingUpload.progress}
</div>
))}
</div>
<input multiple type="file" onChange={handleChange} />
</div>
);
};
2) 解2
- 在 uploadProgress 回调中修改文件并使用修改后的值更新组件的状态
const FileUpload = () => {
const [pendingUploads, setPendingUploads] = useState({});
// files = Record<string, {id: string, progress: string, data: FormData}>
const handleChange = files => {
const handleUploadProgress = (progressEvent, id) => {
const percentCompleted = (progressEvent.loaded * 100) / progressEvent.total;
files[id].progress = `${percentCompleted}%`;
setPendingUploads({ ...files });
};
files.forEach(file => {
axios.post("/files", file.data, {
onUploadProgress: e => handleUploadProgress(e, file.id)
});
});
};
return (
<>
<div>
{_.map(pendingUpload, (pendingUpload, idx) => (
<div key={idx}>
{pendingUpload.id}: {pendingUpload.progress}
</div>
))}
</div>
<input multiple type="file" onChange={handleChange} />
</>
);
};
欢迎任何显示此特定案例最佳实践的反馈或工作示例。
谢谢
您不能在响应回调中使用 pendingUploads 常量,因为它会引用过时的版本。相反,考虑像这样使用以前的状态 setState 签名:setPendingUploads((prevPendingUploads) => {return {...prevPendingUploads, /* updates here */}}
我们想post并行处理多个文件并显示上传进度。
我们第一次尝试解决它是在 uploadProgress 回调中使用 setState 和之前的状态:
const FileUpload = () => {
const [pendingUploads, setPendingUploads] = useState({});
// For clarity: files = Record<string, {id: string, progress: string, data: FormData}>
const handleChange = files => {
setPendingUploads(files);
const handleUploadProgress = (progressEvent, id) => {
const percentCompleted = (progressEvent.loaded * 100) / progressEvent.total;
// Problem: pendingUploads is always empty, we do not get the updated value from last update
setPendingUploads({
...pendingUploads,
[id]: {
...pendingUploads[id],
progress: `${percentCompleted}%`
}
});
};
files.forEach(file => {
axios.post("/files", file.data, {
onUploadProgress: e => handleUploadProgress(e, file.id)
});
});
};
return (
<>
<div>
{_.map(pendingUpload, (pendingUpload, idx) => (
<div key={idx}>
{pendingUpload.id}: {pendingUpload.progress}
</div>
))}
</div>
<input multiple type="file" onChange={handleChange} />
</>
);
};
pendingUploads 始终为空,因此一次仅显示 1 个文件的进度值。
正确的做法是什么?到目前为止,我们有 2 个工作示例:
1) 解决方案 1
- 在状态 (pendingUploads) 中保留待上传地图
- 在 handleUploadProgress(axios 回调)中使用 setState 触发进度更新(progressUpdate)
- 当 progressUpdate 改变时更新 useEffect 中的 pendingUploads
const FileUpload = () => {
const [pendingUploads, setPendingUploads] = useState({});
const [progressUpdate, setProgressUpdate] = useState({ id: "", value: "" });
useEffect(() => {
setPendingUploads({
...pendingUploads,
[progressUpdate.id]: {
...pendingUploads[progressUpdate.id],
progress: progressUpdate.value
}
});
}, [progressUpdate]);
const handleUploadProgress = (progressEvent, id) => {
const percentCompleted = (progressEvent.loaded * 100) / progressEvent.total;
setProgressUpdate({ id, value: `${percentCompleted}%` });
};
// files = Record<string, {id: string, progress: string, data: FormData}>
const handleChange = files => {
setPendingUploads(files);
files.forEach(file => {
axios.post("/files", file.data, {
onUploadProgress: e => handleUploadProgress(e, file.id)
});
});
};
return (
<div>
<div>
{_.map(pendingUploads, (pendingUpload, idx) => (
<div key={idx}>
{pendingUpload.id}: {pendingUpload.progress}
</div>
))}
</div>
<input multiple type="file" onChange={handleChange} />
</div>
);
};
2) 解2
- 在 uploadProgress 回调中修改文件并使用修改后的值更新组件的状态
const FileUpload = () => {
const [pendingUploads, setPendingUploads] = useState({});
// files = Record<string, {id: string, progress: string, data: FormData}>
const handleChange = files => {
const handleUploadProgress = (progressEvent, id) => {
const percentCompleted = (progressEvent.loaded * 100) / progressEvent.total;
files[id].progress = `${percentCompleted}%`;
setPendingUploads({ ...files });
};
files.forEach(file => {
axios.post("/files", file.data, {
onUploadProgress: e => handleUploadProgress(e, file.id)
});
});
};
return (
<>
<div>
{_.map(pendingUpload, (pendingUpload, idx) => (
<div key={idx}>
{pendingUpload.id}: {pendingUpload.progress}
</div>
))}
</div>
<input multiple type="file" onChange={handleChange} />
</>
);
};
欢迎任何显示此特定案例最佳实践的反馈或工作示例。
谢谢
您不能在响应回调中使用 pendingUploads 常量,因为它会引用过时的版本。相反,考虑像这样使用以前的状态 setState 签名:setPendingUploads((prevPendingUploads) => {return {...prevPendingUploads, /* updates here */}}