如何访问已调用 setState 的函数体中的更新状态?
How to access the updated state in the body of a function that has already called setState?
我目前正在编写一个函数,用于将 blogPost
对象保存到我的 Firestore 数据库中的 document
中。
预期的例程应该是:
- 单击保存按钮并调用
savePost()
函数
savePost()
函数应该调用 uploadImagesAndGetURL()
函数,因为 blogPost
的图像应该在我保存它们 src
[=84= 之前上传到 Firebase 存储] 到 Firestore。 (在我的真实情况下,图片上传是 async
,我 await
完成,但我认为这不会影响这里的示例)。
-
uploadImagesAndGetURL()
应该上传图像并将返回的 urls
存储在它们各自的 src
属性 中,在 elements
数组中state
。
- 它通过在
elements
数组上 运行 一个 forEach()
并调用 setState()
(setBlogPost) 用新的 URL 更新状态来做到这一点s 从存储中收到。
- 然后将调用
saveToFirestore()
函数并且应该能够访问最新状态以便能够将 blogPost
对象连同图像 src's
一起保存到数据库中.
问题
我的问题是,虽然我是 运行 savePost()
函数,但在这种情况下组件被重新渲染了 3 次(对于 3 张图像),因为 uploadImagesAndGetURL()
正在调用 setBlogPost()
3 次。每个图像一个。当我到达 saveToFirestore()
函数并记录 blogPost
状态时,您可以看到只有第一张图像 src
发生了变化。
我做的对吗?我怎样才能改进这个例程以确保在 saveToFirestore()
之前所有内容都已更新?
注意: 一种方法是跳过状态更新并将返回的 URLs 直接传递给 saveToFirestore()
函数将它们存储在状态中。但是,在这种情况下合适的模式是什么?
来自 saveToFirestore()
函数的日志:
{
"title": "This is the title",
"body": "Click on the button to upload this post",
"elements": [
{
"type": "image",
"src": "source0"
},
{
"type": "image",
"src": null
},
{
"type": "image",
"src": null
}
]
}
下面的片段
function App() {
console.log('Rendering App...');
const [blogPost,setBlogPost] = React.useState({
title: 'This is the title',
body: 'Click on the button to upload this post',
elements: [
{ type: 'image',
src: null
},
{ type: 'image',
src: null
},
{ type: 'image',
src: null
}
]
});
function savePost() {
console.log('Inside savePost');
uploadImagesAndGetURL();
saveToFirestore();
}
function uploadImagesAndGetURL() {
console.log('Inside uploadImages');
blogPost.elements.forEach((item,index) => {
console.log('Inside uploadImages - forEach');
setBlogPost((prevState)=>{
const aux = Array.from(prevState.elements);
aux[index].src = 'source' + index;
return({
...prevState,
elements: aux
});
});
});
}
function saveToFirestore() {
console.log('Inside saveToFirestore');
console.log('Will save the following blogPost to Firestore');
console.log(blogPost);
}
return(
<React.Fragment>
<div>{blogPost.title}</div>
<div>{blogPost.body}</div>
<div>Image 1 src: {blogPost.elements[0].src}</div>
<div>Image 2 src: {blogPost.elements[1].src}</div>
<div>Image 3 src: {blogPost.elements[2].src}</div>
<button onClick={savePost}>Save Post</button>
</React.Fragment>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
您可以在更改博客状态时在 useEffect
挂钩内移动保存逻辑,它会触发效果 运行。
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
function App() {
console.log("Rendering App...");
const [blogPost, setBlogPost] = React.useState({
title: "This is the title",
body: "Click on the button to upload this post",
elements: [
{ type: "image", src: null },
{ type: "image", src: null },
{ type: "image", src: null }
]
});
function savePost() {
console.log("Inside savePost");
uploadImagesAndGetURL();
// saveToFirestore();
}
function uploadImagesAndGetURL() {
console.log("Inside uploadImages");
const elements = blogPost.elements.map((_, index) => ({
type: "image",
src: `source${index}`
}));
setBlogPost(previousPost => ({ ...previousPost, elements }));
}
// function uploadImagesAndGetURL() {
// console.log("Inside uploadImages");
// blogPost.elements.forEach((item, index) => {
// console.log("Inside uploadImages - forEach");
// setBlogPost(prevState => {
// const aux = Array.from(prevState.elements);
// aux[index].src = "source" + index;
// return {
// ...prevState,
// elements: aux
// };
// });
// });
// }
useEffect(() => {
function saveToFirestore() {
console.log("Inside saveToFirestore");
console.log("Will save the following blogPost to Firestore");
console.log(blogPost);
}
saveToFirestore();
}, [blogPost]);
return (
<React.Fragment>
<div>{blogPost.title}</div>
<div>{blogPost.body}</div>
<div>Image 1 src: {blogPost.elements[0].src}</div>
<div>Image 2 src: {blogPost.elements[1].src}</div>
<div>Image 3 src: {blogPost.elements[2].src}</div>
<button onClick={savePost}>Save Post</button>
</React.Fragment>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
the uploadImagesAndGetURL() is calling setBlogPost() 3 times.
而且我实际上会获取 URL 列表并一次性设置 elements
属性 以防止 re-render 3 次。
我目前正在编写一个函数,用于将 blogPost
对象保存到我的 Firestore 数据库中的 document
中。
预期的例程应该是:
- 单击保存按钮并调用
savePost()
函数 savePost()
函数应该调用uploadImagesAndGetURL()
函数,因为blogPost
的图像应该在我保存它们src
[=84= 之前上传到 Firebase 存储] 到 Firestore。 (在我的真实情况下,图片上传是async
,我await
完成,但我认为这不会影响这里的示例)。-
uploadImagesAndGetURL()
应该上传图像并将返回的urls
存储在它们各自的src
属性 中,在elements
数组中state
。 - 它通过在
elements
数组上 运行 一个forEach()
并调用setState()
(setBlogPost) 用新的 URL 更新状态来做到这一点s 从存储中收到。 - 然后将调用
saveToFirestore()
函数并且应该能够访问最新状态以便能够将blogPost
对象连同图像src's
一起保存到数据库中.
问题
我的问题是,虽然我是 运行 savePost()
函数,但在这种情况下组件被重新渲染了 3 次(对于 3 张图像),因为 uploadImagesAndGetURL()
正在调用 setBlogPost()
3 次。每个图像一个。当我到达 saveToFirestore()
函数并记录 blogPost
状态时,您可以看到只有第一张图像 src
发生了变化。
我做的对吗?我怎样才能改进这个例程以确保在 saveToFirestore()
之前所有内容都已更新?
注意: 一种方法是跳过状态更新并将返回的 URLs 直接传递给 saveToFirestore()
函数将它们存储在状态中。但是,在这种情况下合适的模式是什么?
来自 saveToFirestore()
函数的日志:
{
"title": "This is the title",
"body": "Click on the button to upload this post",
"elements": [
{
"type": "image",
"src": "source0"
},
{
"type": "image",
"src": null
},
{
"type": "image",
"src": null
}
]
}
下面的片段
function App() {
console.log('Rendering App...');
const [blogPost,setBlogPost] = React.useState({
title: 'This is the title',
body: 'Click on the button to upload this post',
elements: [
{ type: 'image',
src: null
},
{ type: 'image',
src: null
},
{ type: 'image',
src: null
}
]
});
function savePost() {
console.log('Inside savePost');
uploadImagesAndGetURL();
saveToFirestore();
}
function uploadImagesAndGetURL() {
console.log('Inside uploadImages');
blogPost.elements.forEach((item,index) => {
console.log('Inside uploadImages - forEach');
setBlogPost((prevState)=>{
const aux = Array.from(prevState.elements);
aux[index].src = 'source' + index;
return({
...prevState,
elements: aux
});
});
});
}
function saveToFirestore() {
console.log('Inside saveToFirestore');
console.log('Will save the following blogPost to Firestore');
console.log(blogPost);
}
return(
<React.Fragment>
<div>{blogPost.title}</div>
<div>{blogPost.body}</div>
<div>Image 1 src: {blogPost.elements[0].src}</div>
<div>Image 2 src: {blogPost.elements[1].src}</div>
<div>Image 3 src: {blogPost.elements[2].src}</div>
<button onClick={savePost}>Save Post</button>
</React.Fragment>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
您可以在更改博客状态时在 useEffect
挂钩内移动保存逻辑,它会触发效果 运行。
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
function App() {
console.log("Rendering App...");
const [blogPost, setBlogPost] = React.useState({
title: "This is the title",
body: "Click on the button to upload this post",
elements: [
{ type: "image", src: null },
{ type: "image", src: null },
{ type: "image", src: null }
]
});
function savePost() {
console.log("Inside savePost");
uploadImagesAndGetURL();
// saveToFirestore();
}
function uploadImagesAndGetURL() {
console.log("Inside uploadImages");
const elements = blogPost.elements.map((_, index) => ({
type: "image",
src: `source${index}`
}));
setBlogPost(previousPost => ({ ...previousPost, elements }));
}
// function uploadImagesAndGetURL() {
// console.log("Inside uploadImages");
// blogPost.elements.forEach((item, index) => {
// console.log("Inside uploadImages - forEach");
// setBlogPost(prevState => {
// const aux = Array.from(prevState.elements);
// aux[index].src = "source" + index;
// return {
// ...prevState,
// elements: aux
// };
// });
// });
// }
useEffect(() => {
function saveToFirestore() {
console.log("Inside saveToFirestore");
console.log("Will save the following blogPost to Firestore");
console.log(blogPost);
}
saveToFirestore();
}, [blogPost]);
return (
<React.Fragment>
<div>{blogPost.title}</div>
<div>{blogPost.body}</div>
<div>Image 1 src: {blogPost.elements[0].src}</div>
<div>Image 2 src: {blogPost.elements[1].src}</div>
<div>Image 3 src: {blogPost.elements[2].src}</div>
<button onClick={savePost}>Save Post</button>
</React.Fragment>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
the uploadImagesAndGetURL() is calling setBlogPost() 3 times.
而且我实际上会获取 URL 列表并一次性设置 elements
属性 以防止 re-render 3 次。