the React tutorial中包含以下代码:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
关于 setState 方法的还有a warning:
setState()并不总是立即更新组件。它可能 批处理或将更新推迟到以后。这使得读取 this.state 在调用 setState()潜在的陷阱之后。
问:是否可能出现以下情况:
还是有某种保护措施可以防止这种情况的发生?
答案 0 :(得分:4)
我希望this 回答您的问题:
在React 16中,如果在React事件处理程序中调用setState,则当React退出浏览器事件处理程序时,它将被刷新。因此,它不是同步的,而是发生在同一顶层堆栈中。
在React 16中,如果您在React事件处理程序外部调用setState,则会立即将其刷新。
让我们看看会发生什么(要点):
handleChange
react事件处理程序; setState
呼叫都在内部进行批处理; handleChange
setState
更改render
被称为handleSubmit
this.state
访问正确提交的值handleSubmit
如您所见,只要在React
事件处理程序中安排了更新,就不会发生竞争状况,因为React
在每个事件结束时都会提交所有批次的state
更新处理程序调用。
答案 1 :(得分:3)
在您的情况下,读取旧值是不可能的。在“它可能会批量更新或将更新推迟到以后”的含义是
this.setState({a: 11});
console.log(this.state.a);
因此setState
可能只是将更改添加到队列中,而不能直接更新this.state
。但这并不意味着您可以通过触发handleChange
来更改输入,然后单击触发handleSubmit
的按钮,而.state
仍未更新。这是因为event loop的工作方式-如果某些代码正在执行,浏览器将不会处理任何事件(您应该遇到UI冻结一段时间的情况)。
因此,重现“竞赛条件”的唯一方法是从另一个运行一个处理程序:
handleChange(event) {
this.setState({value: event.target.value});
this.handleSubmit();
}
这样,是的,您会在警报中看到先前的value
。
在这种情况下,.setState
使用可选的回调参数
setState()的第二个参数是可选的回调函数,将在setState完成并重新呈现组件后执行。通常,我们建议将componentDidUpdate()用于此类逻辑。
适用于您的代码,看起来像
handleChange(event) {
this.setState({value: event.target.value}, this.handleSubmit);
}
PS,请确保您的代码可能会像自己一样延迟setState
和setTimeout
handleChange({target: {value}}) {
setTimeout(() => {
this.setState({value});
}, 5000);
}
并且无法确保handleSubmit
使用最新值。但是在这种情况下,如何处理一切就在您的肩上。
[UPD]有关JS中异步工作原理的一些详细信息。我听过不同的术语:“事件循环”,“消息队列”,“微任务/任务队列”,有时它表示不同的事物(实际上是is a difference)。但是为了使事情变得容易,我们假设只有一个队列。一切与事件处理程序Promise.then()
,setImmediate()
等异步的事件都将移至此队列的末尾。
这样,每个setState
(如果它处于批处理模式)都做两件事:将更改集添加到堆栈(可以是数组变量),并将其他任务设置到队列中(例如setImmediate
)。此附加任务将处理所有堆叠的更改并仅运行一次重新渲染。
即使您在执行那些推迟的更新程序之前很快单击“提交”按钮,事件处理程序也会进入队列的末尾。因此,事件处理程序一定会在应用所有批处理状态的更改后运行。
对不起,我不能仅仅参考React代码来证明,因为更新程序代码对我来说真的很复杂。但是我发现article that has many details是如何运作的。也许它会为您提供一些其他信息。
[UPD]在微任务,宏任务和事件循环方面遇到了不错的文章:https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f?gi=599c66cc504c 它不会改变结果,但会让我更好地理解一切