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 回调没有更新状态的主要内容,如果未能解决你的问题,请参考以下文章
为啥 setState 回调会抛出错误:“来自 useState() 和 useReducer() Hooks 的状态更新不支持第二个回调参数...”