不知道你们有没有过这个疑问,React 中 setState()
为何是异步的?我一度认为 setState()
是同步的,知道它是异步的以后非常困惑,甚至期待 React 能出一个 setStateSync()
之类的 API。一样有此疑问的还有 MobX 的做者 Michel Weststrate,他认为常常听到的答案都很容易反驳,并认为这多是一个历史包袱,因此开了一个 issue 询问真正的缘由。最终这个 issue 获得了 React 核心成员 Dan Abramov 的回复,Dan 的回复代表这不是一个历史包袱,而是一个通过深思熟虑的设计。react
注意:这篇文章根据 Dan 的回复写成,但不是一篇翻译。我忽略了不少不过重要的内容,Dan 的完整回复请看这里。git
Dan 在回复中表示为何 setState()
是异步的,这并无一个明显的答案(obvious answer),每种方案都有它的权衡。可是 React 的设计有如下几点考量:github
首先,我想咱们都赞成推迟并批量处理重渲染是有益并且对性能优化很重要的,不管 setState()
是同步的仍是异步的。那么就算让 state
同步更新,props
也不行,由于当父组件重渲染(re-render )了你才知道 props
。安全
如今的设计保证了 React 提供的 objects(state,props,refs)的行为和表现都是一致的。为何这很重要?Dan 举了个栗子:性能优化
假设 state
是同步更新的,那么下面的代码是能够按预期工做的:bash
console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.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) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
复制代码
这是由于同步模型中,虽然 this.state
会当即更新,可是 this.props
并不会。并且在没有重渲染父组件的状况下,咱们不能当即更新 this.props
。若是要当即更新 this.props
(也就是当即重渲染父组件),就必须放弃批处理(根据状况的不一样,性能可能会有显著的降低)。优化
因此为了解决这样的问题,在 React 中 this.state
和 this.props
都是异步更新的,在上面的例子中重构前跟重构后都会打印出 0。这会让状态提高更安全。
最后 Dan 总结说,React 模型更愿意保证内部的一致性和状态提高的安全性,而不老是追求代码的简洁性。
咱们一般认为状态更新会按照既定顺序被应用,不管 state
是同步更新仍是异步更新。然而事实并不必定如此。
React 会依据不一样的调用源,给不一样的 setState()
调用分配不一样的优先级。调用源包括事件处理、网络请求、动画等。
Dan 又举了个栗子。假设你在一个聊天窗口,你正在输入消息,TextBox
组件中的 setState()
调用须要被当即应用。然而,在你输入过程当中又收到了一条新消息。更好的处理方式或许是延迟渲染新的 MessageBubble
组件,从而让你的输入更加顺畅,而不是当即渲染新的 MessageBubble
组件阻塞线程,致使你输入抖动和延迟。
若是给某些更新分配低优先级,那么就能够把它们的渲染分拆为几个毫秒的块,用户也不会注意到。
Dan 最后说到,异步更新并不仅关于性能优化,而是 React 组件模型能作什么的一个根本性转变(fundamental shift)。
Dan 仍是举了个栗子。假设你从一个页面导航到到另外一个页面,一般你须要展现一个加载动画,等待新页面的渲染。可是若是导航很是快,闪烁一下加载动画又会下降用户体验。
若是这样会不会好点,你只须要简单的调用 setState()
去渲染一个新的页面,React “在幕后”开始渲染这个新的页面。想象一下,不须要你写任何的协调代码,若是这个更新花了比较长的时间,你能够展现一个加载动画,不然在新页面准备好后,让 React 执行一个无缝的切换。此外,在等待过程当中,旧的页面依然能够交互,可是若是花费的时间比较长,你必须展现一个加载动画。
事实证实,在如今的 React 模型基础上作一些生命周期调整,真的能够实现这种设想。@acdlite 已经为这个功能努力几周了,而且很快会发布一个 RFC(亦可赛艇!)。
须要注意的是,异步更新 state
是有可能实现这种设想的前提。若是同步更新 state
就没有办法在幕后渲染新的页面,还保持旧的页面能够交互。它们之间独立的状态更新会冲突。
Dan 最后对 Michel 说到:我但愿咱们能在接下来几个月说服你,而且你会欣赏到 React 模型的灵活性。据我理解,这种灵活性至少一部分要归功于 state
的异步更新。