前言
不晓得大家有没有过这个疑难,React 中 setState()
为什么是异步的?我一度认为 setState()
是同步的,晓得它是异步的之后很是困惑,甚至期待 React 能出一个 setStateSync()
之类的 API。同样有此疑难的还有 MobX 的作者 Michel Weststrate,他认为常常听到的答案都很容易反驳,并认为这可能是一个历史包袱,所以开了一个 issue 询问真正的起因。最终这个 issue 失去了 React 核心成员 Dan Abramov 的回复,Dan 的回复表明这不是一个历史包袱,而是一个通过三思而行的设计。
留神:这篇文章依据 Dan 的回复写成,但不是一篇翻译。我疏忽了很多不太重要的内容,Dan 的残缺回复请看这里。
注释
Dan 在回复中示意为什么 setState()
是异步的,这并没有一个显著的答案(obvious answer),每种计划都有它的衡量。然而 React 的设计有以下几点考量:
一、保障外部的一致性
首先,我想咱们都批准推延并批量解决重渲染是无益而且对性能优化很重要的,无论 setState()
是同步的还是异步的。那么就算让 state
同步更新,props
也不行,因为当父组件重渲染(re-render)了你才晓得 props
。
当初的设计保障了 React 提供的 objects(state,props,refs)的行为和体现都是统一的。为什么这很重要?Dan 举了个栗子:
假如 state
是同步更新的,那么上面的代码是能够按预期工作的:
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
的异步更新。