【发布时间】:2021-08-04 04:06:21
【问题描述】:
我有以下代码:
http-common.ts
import axios from 'axios';
export default axios.create({
baseURL: window.location.origin,
headers: {
'Content-type': 'application/json',
},
});
types.ts
enum UploadStatus {
NONE,
UPLOADING,
DONE,
}
export default UploadStatus;
上传表单.tsx
import * as React from 'react';
import http from './http-common';
import UploadStatus from './types';
type UploadItem = {
file: File,
progress: number,
uploadSuccess: boolean | undefined,
error: string,
};
const sendUpload = (file: File, onUploadProgress: (progressEvent: any) => void) => {
const formData = new FormData();
formData.append('file', file);
return http.post('/api/uploadFile', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress,
});
};
const UploadForm = () => {
const [fileSelection, setSelection] = useState<UploadItem[]>([]);
const [currUploadStatus, setUploadStatus] = useState<UploadStatus>(UploadStatus.NONE);
useEffect(() => {
if (currUploadStatus === UploadStatus.UPLOADING) {
const promises: Promise<void>[] = [];
// Problem code to be discussed
Promise.allSettled(promises)
.then(() => setUploadStatus(UploadStatus.DONE));
}
}, [currUploadStatus]);
const handleFileSelectChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files !== null) {
let newSelection: UploadItem[] = Array.from(event.target.files).map((currFile) => ({
file: currFile,
progress: 0,
uploadSuccess: undefined,
error: '',
isChecked: false,
}));
if (currUploadStatus === UploadStatus.NONE) {
newSelection = newSelection.reduce((currSelection, newSelItem) => {
if (
currSelection.every((currSelItem) => currSelItem.file.name !== newSelItem.file.name)
) {
currSelection.push(newSelItem);
}
return currSelection;
}, [...fileSelection]);
}
setSelection(newSelection);
}
};
// Rest of code
}
我正在尝试将文件从客户端上传到服务器。 sendUpload中的第二个参数onUploadProgress是一个返回当前文件上传进度的函数,让我在屏幕上渲染一个进度条。
在我的第一次尝试中,useEffect 的实现如下:
useEffect(() => {
if (currUploadStatus === UploadStatus.UPLOADING) {
const promises: Promise<void>[] = [];
fileSelection.forEach((currUploadItem) => {
const promise: Promise<void> = sendUpload(
currUploadItem.file,
(event: any) => {
setSelection(() => fileSelection.map((currMapItem) => (
currMapItem.file.name === currUploadItem.file.name
? {
...currMapItem,
progress: Math.round((100 * event.loaded) / event.total),
}
: currMapItem
)));
},
)
.then(({ data }) => {
setSelection(() => fileSelection.map((currMapItem) => (
currMapItem.file.name === currUploadItem.file.name
? {
...currMapItem,
uploadSuccess: data.uploadSuccess,
}
: currMapItem
)));
})
.catch((error) => {
setSelection(() => fileSelection.map((currMapItem) => (
currMapItem.file.name === currUploadItem.file.name
? {
...currMapItem,
error,
uploadSuccess: false,
}
: currMapItem
)));
});
promises.push(promise);
});
Promise.allSettled(promises)
.then(() => setUploadStatus(UploadStatus.DONE));
}
}, [currUploadStatus]);
在上面的useEffect 代码中,我观察到当文件被上传时(即sendUpload(currUploadItem.file, ...) 部分),进度被正确地更新到fileSelection 状态。当我限制浏览器时,我可以看到进度条随着时间的推移正确填满。
但是,一旦 promise 解决并且我到达代码的 .then(...) 部分,进度值会回到 0。在 then(...) 代码段中,uploadSuccess 正确更新为 true,并且此值已成功保存到状态并保持这种状态。换句话说,我可以在屏幕上看到上传成功消息,但我的进度条在达到 100% 后重置为 0%。
在我的第二次尝试中,我将代码更改为如下:
useEffect(() => {
if (currUploadStatus === UploadStatus.UPLOADING) {
const promises: Promise<void>[] = [];
for (const [idx, item] of fileSelection.entries()) {
const promise: Promise<void> = sendUpload(
item.file,
(event: any) => {
const updatedSelection = [...fileSelection];
updatedSelection[idx].progress = Math.round((100 * event.loaded) / event.total);
setSelection(updatedSelection);
},
)
.then(({ data }) => {
const updatedSelection = [...fileSelection];
updatedSelection[idx].uploadSuccess = data.uploadSuccess;
setSelection(updatedSelection);
})
.catch((error) => {
const updatedSelection = [...fileSelection];
updatedSelection[idx] = {
...updatedSelection[idx],
error,
uploadSuccess: false,
};
setSelection(updatedSelection);
});
promises.push(promise);
}
Promise.allSettled(promises)
.then(() => setUploadStatus(UploadStatus.DONE));
}
}, [currUploadStatus]);
在第二个版本中,代码运行良好。 sendUpload(currUploadItem.file, ...) 正确更新我的进度条。当达到 100% 时,上传成功,promise 解析,渲染进度条保持在 100%。 then(...) 完成,uploadSuccess 更新为 true。我的成功消息出现在屏幕上,并且进度条保持满,这是正确的行为。
为什么第一个版本的代码失败了,而第二个版本成功了?在我看来,这两个版本都在做同样的事情:
- 遍历
fileSelection中的每一项。 - 对于每个项目,通过
axiospromise 将文件上传到服务器,并实时获取上传进度 - 当
axios更新上传进度时,创建一个新数组。将旧数组中的项目插入到这个新数组中。对于正在发送文件的数组项,实时更新其进度值。设置状态。 - 上传完成后,
progress为 100。promise 现在解析并执行.then(...)。 - 创建一个新数组。将旧数组中的项目插入到这个新数组中。对于正在发送文件的数组项,
progress仍应为 100。将uploadSuccess更新为从服务器发送的布尔值。设置状态。
我原以为两个版本的代码都在做上述相同的事情。然而由于某种原因,第一个版本在第 4 步保存progress100 到状态,但在第5 步又回到0。第二个版本在第4 步保存progress100 到状态,但在第5 步它保持在100应该的。
发生了什么?
【问题讨论】:
-
在此处粘贴您的代码 jshint.com 在顶部添加 /* jshint esversion: 6 */ 看看我看到了什么。
-
只是好奇,为什么将
currUploadStatus状态传递给useEffect依赖数组?我的意思是这不应该有点像这样的行为:useEffect() - > .then().setState() - > rerender & update currUploadStatus which triggers useEffect() again fails if statement and rerenders the component again还是我错过了什么? -
@zergski
currUploadStatus是一个枚举类型,具有三个可能的值:none、uploading 或 done。无是初始值。提交表单后,它会更改为 Uploading。useEffect中的代码应仅针对此更改执行。当所有的 Promise 都完成后,它会变成 Done。如果用户选择了一组新的文件,屏幕上的一些状态文本会变回无。 -
是的,但是因为
currUploadStatus被传递给依赖数组。当 promises 完成并变为 DONE 时,它不会再次触发 useEffect 钩子,重新渲染组件吗? -
@zergski 是的,useEffect 会再次触发,但是有一个
if (currUploadStatus === UploadStatus.UPLOADING)所以内部代码只有在currUploadStatus更改为专门上传时才会运行。 useEffect 检测到所有其他更改,但不执行任何操作。
标签: javascript reactjs promise axios