在学习react的过程当中几乎全部学习材料都会反复强调一点setState是异步的,来看一下react官网对于setState的说明。react
将
setState()
认为是一次请求而不是一次当即执行更新组件的命令。为了更为可观的性能,React可能会推迟它,稍后会一次性更新这些组件。React不会保证在setState以后,可以马上拿到改变的结果。数据库
一个很经典的例子以下异步
// state.count 当前为 0
componentDidMount(){
this.setState({count: state.count + 1});
console.log(this.state.count)
}
复制代码
若是你熟悉react,你必定知道最后的输出结果是0,而不是1。函数
然而事实真的是这样吗?性能
咱们再来看一个例子学习
class Hello extends Component {
constructor(props) {
super(props);
this.state = { counter: 0 };
}
render() {
return <div onClick={this.onClick.bind(this)}>点我</div>;
}
componentDidMount() {
//手动绑定mousedown事件
ReactDom.findDOMNode(this).addEventListener(
"mousedown",
this.onClick.bind(this)
);
//延时调用onclick事件
setTimeout(this.onClick.bind(this), 1000);
}
onClick(event) {
if (event) {
console.log(event.type);
} else {
console.log("timeout");
}
console.log("prev state:", this.state.counter);
this.setState({
counter: this.state.counter + 1
});
console.log("next state:", this.state.counter);
}
}
export default Hello;
复制代码
在这个组件中采用3中方法更新stateui
在点击组件后,你能够猜到结果吗?输出结果是:this
timeout
"prev state:"
0
"next state:"
1
mousedown
"prev state:"
1
"next state:"
2
click
"prev state:"
2
"next state:"
2
复制代码
codesandbox。spa
结果彷佛有点出人意料,三种方式只有在div上绑定的onClick事件输出了能够证实setState是异步的结果,另外两种方式显示setState彷佛是同步的。prototype
React的核心成员Dan Abramov也在一次回复中提到
这究竟是这么回事?
话很少说,直接上源码,若是你对react源码有必定了解能够接着往下看,若是没有,能够直接跳到结论(如下分析基于react15,16版本可能有出入)。
//代码位于ReactBaseClasses
* @param {partialState} 设置的state参数
* @param {callback} 设置state后的回调
ReactComponent.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.',
);
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
复制代码
在setState
中调用了enqueueSetState
方法将传入的state放到一个队列中,接下来,看下enqueueSetState
的具体实现:
//代码位于ReactUpdateQueue.js
* @param {publicInstance} 须要从新渲染的组件实例
* @param {partialState} 设置的state
* @internal
enqueueSetState: function(publicInstance, partialState) {
//省略部分代码
//从组件列表中找到并返回需渲染的组件
var internalInstance = getInternalInstanceReadyForUpdate(
publicInstance,
'setState',
);
if (!internalInstance) {
return;
}
//state队列
var queue =
internalInstance._pendingStateQueue ||
(internalInstance._pendingStateQueue = []);
//将新的state放入队列
queue.push(partialState);
enqueueUpdate(internalInstance);
},
复制代码
在enqueueSetState
中先是找到需渲染组件并将新的state并入该组件的需更新的state队列中,接下来调用了enqueueUpdate
方法,接着来看:
//代码位于ReactUpdateQueue.js
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
//代码位于ReactUpdates.js
function enqueueUpdate(component) {
ensureInjected();
// Various parts of our code (such as ReactCompositeComponent's
// _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case. (This is called by each top-level update
// function, like setState, forceUpdate, etc.; creation and
// destruction of top-level components is guarded in ReactMount.)
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
复制代码
这段代码就是实现setState
异步更新的关键了,首先要了解的就是batchingStrategy,顾名思义就是批量更新策略,其中经过事务的方式实现state的批量更新,这里的事务和数据库中的事务的概念相似,但不彻底相同,这里就不具体展开了,也是react中颇有意思的内容。 isBatchingUpdates是该事务的一个标志,若是为true,表示react正在一个更新组件的事务流中,根据以上代码逻辑:
那么在事件中调用setState
又为何也是异步的呢,react是经过合成事件实现了对于事件的绑定,在组件建立和更新的入口方法mountComponent和updateComponent中会将绑定的事件注册到document节点上,相应的回调函数经过EventPluginHub存储。 当事件触发时,document上addEventListener注册的callback会被回调,回调函数为ReactEventListener.dispatchEvent,它是事件分发的入口方法。下面咱们来看下dispatchEvent:
dispatchEvent: function (topLevelType, nativeEvent) {
// disable了则直接不回调相关方法
if (!ReactEventListener._enabled) {
return;
}
var bookKeeping = TopLevelCallbackBookKeeping.getPooled(topLevelType, nativeEvent);
try {
// 放入
ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
} finally {
TopLevelCallbackBookKeeping.release(bookKeeping);
}
}
复制代码
看到了熟悉的batchedUpdates方法,只是调用方换成了ReactUpdates,再进入ReactUpdates.batchedUpdates。
function batchedUpdates(callback, a, b, c, d, e) {
ensureInjected();
return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}
复制代码
豁然开朗,原来在事件的处理中也是经过一样的事务完成的,当进入事件处理流程后,该事务的isBatchingUpdates为true,若是在事件中调用setState方法,也会进入dirtyComponent流程,即所谓的异步。
在回过头来看同步的状况,原生事件绑定不会经过合成事件的方式处理,天然也不会进入更新事务的处理流程。setTimeout也同样,在setTimeout回调执行时已经完成了原更新组件流程,不会放入dirtyComponent进行异步更新,其结果天然是同步的。
顺便提一下,在更新组建时,将更新的state合并到原state是在componentWillUpdate以后,render以前,因此在componentWillUpdate以前设置的setState
能够在render中拿到最新值。
1.在组件生命周期中或者react事件绑定中,setState是经过异步更新的。 2.在延时的回调或者原生事件绑定的回调中调用setState不必定是异步的。
这个结果并不说明setState异步执行的说法是错误的,更加准确的说法应该是setState不能保证同步执行。
Dan Abramov也屡次提到从此会将setState完全改造为异步的,从js conf中提到的suspend新特新也印证了这一点。