react setState 回调没有更新状态

Posted

技术标签:

【中文标题】react setState 回调没有更新状态【英文标题】:react setState callback doesn't have the updated state 【发布时间】:2019-01-28 05:25:15 【问题描述】:

如果monthOffset = 12,条件将评估为真,如果yearOffset = 2018,则将yearOffset 状态更新为2017。根据我阅读的反应文档和其他答案,this.setState 中的回调函数在状态更新后触发,但console.log() 仍在输出 2018。我尝试了几种不同的方法来实现此代码基于其他相关问题的答案,但我的不起作用。我不知道为什么。

handleClick(e) 
  const  monthOffset, yearOffset  = this.state
  this.setState( monthOffset: monthOffset - 1 )
  if ( monthOffset - 1 === 11 )  this.setState((prevState) =>  
    return  yearOffset: prevState.yearOffset - 1  ,
    () => console.log("yearOffset", yearOffset)
  )
  console.log("clicked")

【问题讨论】:

您是否尝试过在 setState 回调中使用 console.log("yearOffset", yearOffset) 而不是匿名函数? 【参考方案1】:

文档说回调始终有效,但我从经验中知道它并不总是返回您所期望的。我认为这与在状态本身中使用可变对象有关。

文档:https://reactjs.org/docs/react-component.html#setstate

你不能完全依赖回调。相反,您可以做的是创建

var stateObject = this.state

对对象进行任何必要的更改:

stateObject.monthOffset -= 1

然后像这样设置状态:

this.setState(stateObject);

这样您就可以在stateObject 中获得nextState 的副本

澄清:您想在设置状态之前进行所有评估,所以这样做:

monthOffset -= 1

然后if (monthOffset === 12) yearOffset -=1;

然后var stateObj = monthOffset: monthOffset, yearOffset: yearOffset

然后this.setState(stateObj);


来自文档: "setState() 的第二个参数是一个可选的回调函数,一旦 setState 完成并重新渲染组件就会执行。一般我们建议使用componentDidUpdate() 代替这种逻辑。"

所以基本上如果你想获得下一个状态,你应该在调用setState()的函数中拥有它的副本,或者你应该从componentDidUpdate获取nextState


事后思考:您作为参数传递给setState() 的任何内容通过引用传递(而不是通过值)。所以,如果你在你的状态中有一个对象SearchFilters: ,并且在你对setState()的调用中,你有

setState(SearchFilters: DEFAULT_SEARCH_FILTERS); // do not do this

您可能已将SearchFilters 设置为DEFAULT_SEARCH_FILTERS 以清除名为“搜索过滤器”的表单,但您实际上将DEFAULT_SEARCH_FILTERS(常量)设置为SearchFilters,清除了您的DEFAULT_SEARCH_FILTERS.

预期的行为?你告诉我。

【讨论】:

【参考方案2】:

在 React 中调用 setState 有两种流行的模式:对象 setState 和 "functional setState"。 Functional setState is generally used when the current state(或“先前状态”,或任何你想调用的旧状态)is invoked in the setState call. 这是因为 setState 是异步的,因此后续 setStates 有时可以在 React 成功完成之前运行第一个setState 循环。

您在setState 调用中使用了现有状态,因此它是使用功能性setState 的合适位置。替换

this.setState( monthOffset: monthOffset - 1 )

this.setState(monthOffset => return monthOffset: monthOffset - 1)

如果你和我一样,当我第一次看到这个时,你可能会想,“嗯?这和我有什么不同?”不同之处在于,当 setState 被传递一个函数而不是一个对象时,它会将更新排队而不是通过通常的解析过程,从而确保事情按顺序完成。

或者你可能不这么想;您实际上在第二次 setState 通话中使用了功能性 setState。在你的第一个中使用它也将确保事情正确排队。

【讨论】:

【参考方案3】:

也许您可以通过以下方式简化您的逻辑,以避免多次调用setState,这可能会导致意外结果:

handleClick(e) 

  const  monthOffset, yearOffset  = this.state

  // call setState once
  this.setState( 

    // Always decrement month offset
    monthOffset : monthOffset - 1, 

    // Only decrement year offset if current month offset === 12
    yearOffset : (monthOffset === 12) ? yearOffset - 1 : yearOffset

  , () => 

    console.log("state updated to", this.state)
  )

  console.log("clicked")

【讨论】:

这是一个更有效的代码变体,我得到了我想要的结果。我认为问题在于当我调用console.log("yearOffset", yearOffset) 时,它正在读取来自const 析构函数的值,我认为在调用setState 后它没有更新。也许为了使我的变体起作用,我必须在回调中创建一个新的析构函数。 请注意,除了将信息记录到控制台之外,您不应出于任何原因使用回调来获取信息。回调并不总是有效。您还可以使用解构器从状态中提取变量以获得更有效的解决方案,但根据我的经验,我从未注意到复制整个状态本身有任何延迟。 @ihodonald 但是文档不是说它总是在状态更新后调用吗?我认为文档是正确的,我只是从析构函数中读取错误的值。正如我上面所说,我需要在回调函数中使用this.state 的新析构函数来获取新值。我试过了,它确实有效。然而,这个答案是使用条件和回调的更好方法,所以我最终使用了它。 回调在大多数情况下确实有效,但对于需要在下一个状态执行的任何其他类型的逻辑,它不应该完全依赖。谨慎使用它。您可以将它用于控制台的简单日志,但我不相信它每次都返回正确的值。文档还说您应该使用 componentDidUpdate 来获取 nextState。您总是可以将状态中想要的内容解构为变量(或常量),更改该变量,然后将 nextState 放入该变量中。 @RyanSam 这不能回答你的问题,但它可能会给你一些见解:reactjs.org/docs/reconciliation.html#the-diffing-algorithm

以上是关于react setState 回调没有更新状态的主要内容,如果未能解决你的问题,请参考以下文章

ReactJS 状态未更新在 setState 回调中

为啥 setState 回调会抛出错误:“来自 useState() 和 useReducer() Hooks 的状态更新不支持第二个回调参数...”

更新后如何将函数传递给 SetState 回调? (反应)

setstate 回调后 useState 不改变?

setState详解

如何在 Redux 中更新状态后触发回调?