如何访问已调用 setState 的函数主体中的更新状态?

Posted

技术标签:

【中文标题】如何访问已调用 setState 的函数主体中的更新状态?【英文标题】:How to access the updated state in the body of a function that has already called setState? 【发布时间】:2019-10-25 22:42:12 【问题描述】:

我目前正在编写一个函数,用于将 blogPost 对象保存到我的 Firestore 数据库中的 document 中。

预期的例程应该是:

点击保存按钮并调用savePost()函数 savePost() 函数应该调用 uploadImagesAndGetURL() 函数,因为在我将 src URL 保存到 Firestore 之前,应该将 blogPost 的图像上传到 Firebase 存储。 (在我的真实情况下,图片上传是async 和我await 来完成,但我认为这不会影响这里的示例)。 uploadImagesAndGetURL() 应该上传图像并将返回的urls 存储在它们各自的src 属性中,在stateelements 数组中。 它通过在 elements 数组上运行 forEach() 并调用 setState() (setBlogPost) 以使用从存储中接收到的新 URL 来更新状态。 然后将调用saveToFirestore() 函数,并且应该能够访问最新状态,以便能够将blogPost 对象与图像src's 一起保存到数据库中。

问题

我的问题是,当我运行 savePost() 函数时,在这种情况下组件被重新渲染 3 次(对于 3 张图像),因为 uploadImagesAndGetURL() 正在调用 setBlogPost() 3 次。每个图像一个。当我到达saveToFirestore() 函数并记录blogPost 状态时,您可以看到只有第一张图像src 发生了变化。

我做得对吗?我如何改进此例程以确保在saveToFirestore() 之前所有内容都已更新?

注意: 一种方法是跳过状态更新并将返回的 URL 直接传递给 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>

【问题讨论】:

为什么不用效果钩子来触发saveToFirestore? 效果会基于什么运行?因为我有多个setState() 电话?我可以创建一个带有hasUploaded 布尔值的状态标志并基于它运行效果。但即使我在 src 的所有 setState 之后更新了这个布尔值,我可以确定这些 setState 调用的顺序并且 hasUploaded 将是最后一个应用吗? React 批量调用setState,因为它发生在事件处理程序中,所以效果挂钩只会在整个更新后运行一次。 @Remeus 即使setState 调用在事件处理程序内部更深一层,批处理也会工作吗?就像我的情况一样,处理程序是savePost,它调用uploadImagesAndGetURL,然后它调用setState 只要 React 可以确定何时会进行所有状态更改(例如没有异步),就应该是的。 【参考方案1】:

您可以在更改博客状态时在 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"));

uploadImagesAndGetURL() 调用 setBlogPost() 3 次。

而且我实际上会获取 URL 列表并一次性设置 elements 属性以防止重新渲染 3 次。

【讨论】:

这是一个很好的解决方案,但在我的真实案例中,blogPost 也会因其他原因而发生变化。请参阅对我写的问题的评论。 是否可以在uploadImagesAndGetURL 中生成blogPost 的新实例,并将其传递给setBlogPost& saveToFireStore?它消除了调用useEffect 的需要,并表明saveToFireStore 明确依赖于博客文章。 好主意!我试试看!

以上是关于如何访问已调用 setState 的函数主体中的更新状态?的主要内容,如果未能解决你的问题,请参考以下文章

如何在我的 setState 函数调用中使用变量?

Flutter:在构造函数中调用 setState():_SharesListState#6c96a(生命周期状态:已创建,无小部件,未安装)

在已卸载的组件中发出有关setState的警告

如何知道所有 setState 更新是不是已应用于 React 组件中的状态?

如何执行在子组件中调用的函数作为反应中的属性?

多次调用函数时 setState 不会发生变化