当前位置: 首页 > Web前端 > HTML

Q:为什么React的setState是异步的?

时间:2023-03-28 02:05:52 HTML

前言不知道大家有没有遇到过这样的疑问,为什么React中的setState()是异步的?曾经以为setState()是同步的,但知道是异步后就很迷茫,甚至期待React有setStateSync()这样的API。MobX的作者MichelWeststrate也有这个疑问。他认为自己经常听到的答案很容易反驳,觉得这可能是历史包袱,于是开了个issue问问真正的原因。最终,这个问题得到了React核心成员DanAbramov的回复。Dan的回复说明这不是历史包袱,而是深思熟虑的设计。注意:本文基于Dan的回复,而非翻译。我省略了很多不必要的东西,丹的完整回复可以在这里找到。文丹在回复中说为什么setState()是异步的,没有明显的答案(obviousanswer),每个方案都有它的取舍。但React的设计有以下几点考虑:1.确保内部一致性首先,我想我们都可以同意推迟和批量重新渲染对于性能优化是有益和重要的,无论setState()是同步的还是异步的。那么即使同步更新了state,props也不起作用,因为你只有在父组件重新渲染(re-render)的时候才会知道props。当前的设计确保了React提供的对象(state、props、refs)行为和行为一致。为什么这很重要?Dan举了一个例子:假设状态是同步更新的,下面的代码按预期工作:console.log(this.state.value)//0this.setState({value:this.state.value+1});console.log(this.state.value)//1this.setState({value:this.state.value+1});console.log(this.state.value)//2但是,这时候你需要将状态提升到父组件,供多个兄弟组件共享:-this.setState({value:this.state.value+1});+this.props.onIncrement();//在父组件中做同样需要指出的是,在React应用中,这是一个非常常见的重构,几乎每天都在发生。然而,下面的代码没有按预期工作:console.log(this.props.value)//0this.props.onIncrement();console.log(this.props.value)//0this.props.onIncrement();console.log(this.props.value)//0这是因为在同步模型中,虽然this.state会立即更新,但this.props不会。并且我们不能在不重新渲染父组件的情况下立即更新this.props。如果你想立即更新this.props(即立即重新渲染父组件),你必须放弃批处理(根据情况,可能会有显着的性能损失)。所以为了解决这个问题,React中this.state和this.props都是异步更新的。在上面的例子中,重构前后都会打印0。这使得状态提升更安全。最后,Dan总结说React模型更愿意保证内部一致性和状态提升的安全性,而不是一直追求代码的简单性。2.性能优化我们通常认为状态更新会按照预定的顺序应用,无论状态是同步更新还是异步更新。然而,情况并非一定如此。React根据调用的来源为不同的setState()调用分配不同的优先级。调用源包括事件处理、网络请求、动画等。丹又举了一个栗子。假设您在聊天窗口中输入消息,则需要立即应用TextBox组件中的setState()调用。但是,在您键入时收到一条新消息。更好的方法可能是延迟渲染新的MessageBubble组件,以便您的输入更流畅,而不是立即渲染新的MessageBubble组件以阻塞线程并导致您的输入抖动和滞后。如果您为某些更新分配低优先级,它们可以以几毫秒的块呈现,用户不会注意到。详见前端高级面试题答案3.更多可能性Dan最后说异步更新不仅仅是性能优化,而是React组件模型能做的事情的根本性转变。丹还是举了个栗子。假设你从一个页面导航到另一个页面,通常你需要显示一个加载动画并等待新页面被渲染。但是如果导航速度非常快,闪烁的加载动画会降低用户体验。如果这很好,您只需调用setState()来渲染一个新页面,然后React开始“在幕后”渲染新页面。想象一下,你不必编写任何协调代码,如果更新需要很长时间,你可以显示一个加载动画,否则让React在新页面准备好时执行无缝过渡。另外,在等待的过程中,老页面还是可以交互的,但是如果时间比较长,就必须展示loading动画。事实证明,基于当前React模型的一些生命周期调整,确实可以实现这一愿景。@acdlite已经在这个功能上工作了几个星期,并将很快发布一个RFC(也是赛艇!)。需要注意的是,异步更新状态是这个愿景成为可能的先决条件。如果状态是同步更新的,就没有办法在后台渲染新页面,保持旧页面交互。它们之间的独立状态更新会发生冲突。Dan最后对Michel说:我希望我们能在接下来的几个月里说服你,你会欣赏React模型的灵活性。据我了解,这种灵活性至少部分归因于异步状态更新。