我想:
- 将图像上传到浏览器中(我使用 React Dropzone 执行此操作。我下面的
uploadImage
函数在将文件拖放到拖放区时运行。 - 使用文件读取器 API 将图像作为实际图像读取。
- 使用 React Hooks 将图像文件和图像元数据保存到 state 中。
- 进行 API 调用,将此映像推送到服务器。
我正在尝试确认我在步骤 4 中拥有图像,但我无法通过控制台消息确认。尽管日志消息显示状态已更新,但我还是以某种方式坚持使用原始状态值。
我尝试为所有内容添加额外的异步,并且我已经将内容包装在承诺中。我可以确认readImage
功能正常。我可以确认运行序列是readImage
、saveImage
、setGraphic
在 saveImage 中,然后是控制台.log在uploadImage
中应该具有更新的值但没有。
const [graphic, setGraphic] = useState({ ...nullGraphic });
const readImage = async (img) => {
const reader = new FileReader();
return new Promise((resolve, reject) => {
reader.onerror = () => reader.abort();
reader.onabort = () => reject(new DOMException('Problem parsing input file.'));
reader.onload = () => resolve(reader.result);
reader.readAsDataURL(img);
});
};
const saveImage = async (img) => {
const imageRead = await readImage(img);
console.log(imageRead);
await setGraphic({
...graphic,
image: imageRead,
imageName: img.name,
imageType: img.type,
});
};
const uploadImage = async (img) => {
await saveImage(img);
console.log(graphic);
});
render() {
console.log(graphic);
return( <some dom> )
}
这是我奇怪的控制台序列:
Form.js:112 starting saveImage
Form.js:95 (snipped value of imageRead)
Form.js:237 {snipped updated values of graphic}
Form.js:114 done with saveImage
Form.js:116 {snipped original values of graphic}
我的理论
useState 钩子正在做一些时髦的事情。 进程在启动时复制值,而不是在读取控制台.log时读取该值,因此uploadImage
函数在该进程的生存期内获取 useState 挂钩的原始值。
如果我们将所有这些 React 魔术简化为:
function withState(initial, fn) {
function setState(updated) {
setTimeout(() => fn(updated, setState), 0);
//...
}
setState(initial);
}
现在我们可以说明发生了什么:
withState(0, (counter, setCount) => {
console.log("before", counter);
if(counter < 10) setCount(counter + 1);
console.log("after", counter);
});
如您所见,setCount
不会直接更改状态(count
)。它实际上根本不会改变局部count
变量。因此,"之前"和"之后"将始终记录相同的内容(只要您不直接counter
突变)。
相反,调用setCount
将被延迟(并在 React 中批处理),然后它会调用传入新计数的整个组件函数。
因此,尝试await
setState 调用是没有意义的,在它之前和之后记录状态也是没有意义的(因为它不会在两者之间更改)。在组件中记录一次,如果设置状态,则整个组件将再次执行,并且日志记录将记录新状态。