首页 > 解决方案 > 理解:React 状态更新时机

问题描述

我将在这里假设我的问题在于 React 状态如何工作的异步性质(至少我希望这是一个正确的陈述)。我有一个应用程序,我在其中创建了一个 UI,其中有 4 个带有值的按钮和一个“确定”按钮。用户尝试通过单击相应的值按钮来选择最大值,然后单击“确定”以确认他们的选择。

React 何时、为什么以及如何更新我的this.setState({ value: this.state.chosenButton });声明?因为在

if (...) {
  //...
} else {
  this.onAnswer(this.state.value, item.id);
}

部分值仍未更新。

我尝试创建一个名为的单独函数stateUpdated,该函数包含setState调用、超时和其他延迟执行以允许状态更新的方式,但似乎问题不是基于时间的,而是完全基于其他的。

我也知道我可以chosenButton在最后一个 else 语句中使用而不是,value但我更感兴趣的是理解“为什么?” 这个问题,而不是如何“修复”我的代码。

keyInput(event) {
    const moduleState = StudentModuleState;
    const item: Item = moduleState.displayedItems[0];
    const practice: boolean = !StudentModuleState.itemSet.assessment_set;

    if (!this || !this._isMounted) { return; }
    this.setState({ value: this.state.chosenButton }); 

    if (practice) {
        if (this.state.chosenButton === item.correct) {
            this.setState({ answerCorrect: true })
            setTimeout(() => this.progressHandler(), 2000);
        } else {
            this.setState({ answerWrong: true, })
            setTimeout(() => this.progressHandler(), 2000);
        }
    } else {
        this.onAnswer(this.state.value, item.id);
    }
}

标签: javascriptreactjstypescript

解决方案


我将在这里假设我的问题在于 React 状态如何工作的异步性质......

这是正确的。状态更新是异步的,因此紧跟在setState调用之后的代码仍会看到旧状态。要等到更新,请使用更新回调(的第二个参数setState):

keyInput(event) {
    const moduleState = StudentModuleState;
    const item: Item = moduleState.displayedItems[0];
    const practice: boolean = !StudentModuleState.itemSet.assessment_set;

    if (!this || !this._isMounted) { return; }
    this.setState(
        { value: this.state.chosenButton },
        () => {
            if (practice) {
                if (this.state.chosenButton === item.correct) {
                    this.setState({ answerCorrect: true })
                    setTimeout(() => this.progressHandler(), 2000);
                } else {
                    this.setState({ answerWrong: true, })
                    setTimeout(() => this.progressHandler(), 2000);
                }
            } else {
                this.onAnswer(this.state.value, item.id);
            }
        }
    ); 
}

关于这一点的旁注:

    this.setState(
        { value: this.state.chosenButton },
        // ------^^^^^^^^^^^^^^^^^^^^^^^

看来您正在更新状态以响应按钮按下(记住按下了哪个按钮),然后使用该更新的状态来响应键盘事件。没关系,只是因为 React 专门处理它:它保证响应 a 的先前状态更改click将在下一个事件被调度之前呈现(并因此应用)。这些过去称为“交互式”事件,但现在称为“离散”事件,您可以在此处找到列表。请注意,这是针对click各种键盘事件,而不是针对mousemove. 此 twitter 线程中的详细信息,Dan Abramov(React 项目的核心提交者)在其中写道:

即使在并发模式下,我们确实保证 React 事件(例如“点击”)和其他暗示用户有意操作的事件会在处理下一个事件之前刷新。您的“残疾”示例是动机之一。


请注意,我们不保证同步处理首次点击。只是如果您下次单击,我们将确保在决定是否处理下一个事件或忽略它之前刷新第一个的结果。


您可以在此处找到此类事件的列表。(现在在代码中称为“交互式”,尽管这可能不是最好的命名)。https://github.com/facebook/react/blob/master/packages/react-dom/src/events/SimpleEventPlugin.js


对于像“mousemove”这样的事件是连续的而不是离散的,我们不做这样的保证。对于那些我们认为批处理是安全的,有时会跳过中间的,因为用户不会故意将每个移动视为一个单独的事件。


另请注意,在并发模式中,这些保证仅对 React 事件强制执行。如果您通过 addEventListener() 手动订阅,则需要做一些额外的事情才能拥有它们。

但是,今天(在同步模式下)那些总是同步的。所以只是为了未来。


推荐阅读