【问题标题】:How to access the updated state in the body of a function that has already called setState?如何访问已调用 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 可以确定何时会进行所有状态更改(例如没有异步),就应该是的。

标签: javascript reactjs firebase google-cloud-firestore react-hooks


【解决方案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 明确依赖于博客文章。
  • 好主意!我试试看!
猜你喜欢
  • 2021-06-01
  • 2022-12-09
  • 2018-12-08
  • 2019-04-09
  • 2019-10-17
  • 1970-01-01
  • 2018-07-25
  • 2017-09-16
  • 1970-01-01
相关资源
最近更新 更多