一张图看懂React setState操作
网上关于react setState的结论不少,比如:
- setState不会立刻改变React组件中state的值;
- 多次setState函数调用产生的效果会合并。
但你是否真的了解setState背后的机制?真的是setState触发的刷新吗?
废话不说,先上图
组件挂载后,setState一般是通过DOM交互事件触发。这里以click
为例,其他也一样。
代码很简单
import React, {Component} from 'react';
class MyInfo extends Component{
constructor(props,context){
super(props,context);
this.state = {
age:1
}
}
_grow(age){
age++
this.setState({
age:age
})
}
render(){
const {age} = this.state
return (
<div>
我的年龄是{age}
<button onClick={this._grow.bind(this,age)}>点击涨一岁</button>
</div>
)
}
}
export default MyInfo;
我们点击button按钮时,到底发生了什么?ReactEventListener会触发dispatchEvent方法。(具体怎么触发是事件机制的事,这里不深究)
dispatchEvent: function (topLevelType, nativeEvent) {
if (!ReactEventListener._enabled) {
return;
}
var bookKeeping = TopLevelCallbackBookKeeping.getPooled(topLevelType, nativeEvent);
try {
// Event queue being processed in the same cycle allows
// `preventDefault`.
ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
} finally {
TopLevelCallbackBookKeeping.release(bookKeeping);
}
}
可以看到这里有个ReactUpdates.batchedUpdates
方法。我们跟进去看看
function batchedUpdates(callback, a, b, c, d, e) {
ensureInjected();
return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}
可以发现这里调用了batchingStrategy
的方法。这又是什么鬼,其实这是注入进来的ReactDefaultBatchingStragy
(这里插一句,React大量运用了注入机制,这样每次注入的都是同一个实例化对象,防止多次实例化。)
到这边就已经开启了批量更新模式
继续看,
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// The code is written this way to avoid extra allocations
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
},
transaction.perform
执行了一个事务。事务其他文章说的很多我就不详细解释了。大概就是,transaction在执行perform之前会执行特性的initialize方法,然后执行传进去的callback,之后会执行close方法,是不是似曾相识?没错,高阶函数或者高阶组件都是这路数。
在ReactDefaultBatchingStragy
里可以发现
var transaction = new ReactDefaultBatchingStrategyTransaction();
//在事务结束时清理一下标识
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function() {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
},
};
// 在事务结束时执行flushBatchedUpdates方法,这个方法就是 state 更新的核心代码了。
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
function ReactDefaultBatchingStrategyTransaction() {
this.reinitializeTransaction();
}
Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
});
可以发现这个transition有两个wrapper,主要看FLUSH_BATCHED_UPDATES
目前走完了图中的第一行,有点晕的可以对着图回顾一下。
-
是不是发现哪里不对?是的,到现在我们的setState还没执行呢!
接着上文,我们首先看看transition的两个initailize方法,发现时两个空函数。跳过
接着就是perform需要执行的逻辑了。再次放出代码
ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
也就是执行这边的handleTopLevelImpl
。正是在这边调用DOM事件对应的回调方法。也就是例子中_grow
在这时候调用。
然后是setState方法。这里和大部分书和文章说的差不多。抛开细节就是将state的变化和对应的回调函数放置到_pendingStateQueue
,和_pendingCallback
中。
然后把需要更新的组件放到dirtyComponents序列中。
重点来了:
注意注意!!!!
setState从来不负责更新操作。它的工作只是把state,和callback放进序列,并且把要更新的组件放到dirtyComponents序列
还记得吗?我们还在ReactDefalutBatchingStragy
的事务中,perform执行完了,还要执行close。
真正执行更新方法的是close里面的flushBatchedUpdates
。
鉴于文章长度,其他的可以看图理解
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。