【问题标题】:Why react doesn't update state in "then" promise?为什么反应不更新“那么”承诺中的状态?
【发布时间】:2020-11-05 15:03:55
【问题描述】:

我有一个表格,每行都有Delete 按钮。当您删除该行时,客户端将异步发送请求执行 API。这意味着,如果您快速删除多行,您不必等待一个请求完成,再发送另一个删除请求。当您单击Delete 按钮并发送请求时,该按钮将被禁用(在响应返回之前该行将消失)。

这是处理Container中删除的函数:

  const [deletingTeams, setDeletingTeams] = useState([]);

  const handleDelete = (teamId) => {
    setDeletingTeams([...deletingTeams, teamId]);

    deleteTeam(teamId).then(() => {    
      console.log(deletingTeams); <---- This prints EMPTY array. Why????   
      let newState = deletingTeams.filter((id) => id !== teamId);
      setDeletingTeams(newState);
    });
  };

非常简单。 deletingTeams 是一个数组,其中包含当前正在删除的团队的 ID(响应没有从该 API 返回,删除成功)。

在第 3 行 (setDeletingTeams([...deletingTeams, teamId]);) 我将新的 teamId 添加到数组中,它可以工作。新数组正在传递给List 组件,Delete 按钮确实被禁用。 但是...

...当响应返回时,在 then 承诺中,我想从数组中删除 teamId。问题是,deletingTeams 数组已经为空(在过滤器之前)。为什么??

谢谢你的解释,

【问题讨论】:

    标签: javascript reactjs redux async-await es6-promise


    【解决方案1】:

    状态更新是异步的 - 不能保证您立即看到更新状态的最新值。

    使用回调的方式过滤和设置状态。

    const [deletingTeams, setDeletingTeams] = useState([]);
    
    const handleDelete = (teamId) => {
      setDeletingTeams([...deletingTeams, teamId]);
    
      deleteTeam(teamId).then(() => {
        // console.log(deletingTeams); //<---- state updates are async - you will see the latest value of the state in the next re-render
        // let newState = deletingTeams.filter((id) => id !== teamId); //<---- remove this
        setDeletingTeams((prev) => prev.filter((id) => id !== teamId)); //<--- use call back approach to set the state
      });
    };
    
    

    【讨论】:

    • 太棒了!那行得通。只是后续问题:我应该与setDeletingTeams([...deletingTeams, teamId]); 相同(使用以前的状态)吗?
    • 是的,您可以 .. 更安全... 只需执行 setDeletingTeams(prev =&gt; ([...prev, teamId])) ..... 通常,当您处理快速更新状态并且状态更新取决于上一个状态时或者组件的prop,需要使用回调函数,以便状态完美更新...
    【解决方案2】:

    简而言之,发生这种情况的原因是因为deletingTeams 在您的异步调用之前 获得了它的值。原因很微妙!

    实际情况是,当您在 then 函数中为 deleteTeam 使用 detetingTeams 时,您传入的回调函数正在使用名为 function closures 的 JavaScript 功能。变量 deletingTeams 用于您的 then 函数 - 但它不是由该函数定义的!所以它不能有本地函数范围。那么它究竟是从哪里来的呢?

    答案很微妙。该变量在您的第一行中定义:const [deletingTeams, setDeletingTeams] = useState([]);。此时,根据组件的当前状态为变量分配一个值 - 删除之前您当前拥有的团队。然后,您在函数中引用它。此时,变量deletingTeams 被添加到then 函数的作用域中。它是对函数外同一个变量的引用!

    好的,所以deletingTeams 是一个空数组的原因是因为当您创建then 函数时它是一个空数组,对吧?不完全的。对deletingTeams 的任何更改都会在您执行then 函数时反映出来。但在您的示例中,deletingTeams 永远不会改变它的值。当您调用setDeletingTeams 时,您的组件的状态会发生变化,但您只查找一次状态 - 在代码块的开头。没有什么魔法可以改变deletingTeams'的价值;为此,您必须再次致电useState

    deletingTeams 每次重新渲染组件时都会被赋予一个新值。但不幸的是,then 函数的闭包范围只确定了一次。 (每次重新渲染时,实际上都会生成一个新的then 函数,其范围为该渲染版本的deletingTeams 变量!)组件可能已经重新渲染,但then 块仍以相同的陈旧值调用。

    所以,总的来说,这就是发生的事情:

    1. deletingTeams 在您的第一行中分配了一个值:const [deletingTeams, setDeletingTeams] = useState([]);
    2. 您使用setDeletingTeams 更新状态,但useState 只调用一次,因此deletingTeams 永远不会更新以匹配新值。
    3. deletingTeams 在组件的每次新渲染中都会获得一个新值,但不幸的是,到那时,您的 then 函数已经绑定到一个特定的、过去的 deletingTeams 变量,该变量永远不会更新。
    4. 因此,当您的 then 块执行时,deletingTeams 的值已过时。

    那么,正确的方法是什么?我们可以读取then函数中的状态来获取deletingTeams的最新值。这只是this.state.deletingTeams。这里有更多信息:https://reactjs.org/docs/hooks-state.html#reading-state

    (因为您使用的是箭头函数,then 函数会自动使用与组件相同的 this,所以一切都应该正常工作。)

    所以这应该可以解决问题:

      const [deletingTeams, setDeletingTeams] = useState([]);
    
      const handleDelete = (teamId) => {
        setDeletingTeams([...deletingTeams, teamId]);
    
        deleteTeam(teamId).then(() => {    
          let newState = this.state.deletingTeams.filter((id) => id !== teamId);
          setDeletingTeams(newState);
        });
      };
    

    (在这种情况下,请务必使用this.state.deletingTeams,而不是再次调用useState。在嵌套函数或回调中调用useState可能会导致错误:https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

    【讨论】:

      猜你喜欢
      • 2021-09-30
      • 2021-02-17
      • 1970-01-01
      • 2023-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-25
      相关资源
      最近更新 更多