法国队时隔20年夺得了世界杯冠军,这个使人兴奋的消息到如今还在我心里不能平息,矫情事后开始着手分析一波,固然了分析的比较拙劣,但愿能给学习react的人送去一点启发和但愿,写的有所纰漏,不足之处还但愿知识储备是我几倍的大牛们们指出。javascript
正如你们一致公认的react是以数据为核心的,所以说到组件更新的时候我下意识的会想到当状态变化的时候会进行组件更新。除了经过redux进行状态管理从而进行组件更新外还有像我这种菜鸡经过
setState进行状态修改,关于setState,相信有很多人一开始和我有同样的疑惑,为什么不能经过普通的this.state来进行状态更新,在开始前首先思考个问题,下面的打印结果分别是多少html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="./js/react.js"></script> <script src="./js/react-dom.js"></script> <script src="./js/browser.js"></script> <script type="text/babel"> class Comp extends React.Component{ constructor(...args) { super(...args); this.state = {i: 0} } render(){ return <div onClick={() => { this.setState({i: this.state.i + 1}) console.log('1',this.state.i); // 1 this.setState({i: this.state.i + 1}) console.log('2',this.state.i); // 2 setTimeout(() => { this.setState({i: this.state.i + 1}) console.log('3',this.state.i) // 3 this.setState({i: this.state.i + 1}) console.log('4',this.state.i) // 4 },0); this.setState((prevState, props) => ({ i: prevState.i + 1 })); }}>Hello, world!{this.state.i} <i>{this.props.name}, 年龄{this.props.age}</i></div>; } } window.onload = function(){ var oDiv = document.getElementById('div1'); ReactDOM.render( <Comp name="zjf" age='24'/>, oDiv ); } </script> </head> <body> <div id="div1"></div> </body> </html>
这个虽然寥寥几个参数却对我来讲像解决奥数题那样复杂,1234? 0012? 。。心里的无限热情也就在这个时候随着夏季的温度上升再上升了,默默打开了源代码,其中的实现真的能够说amazing,带着这个疑问让咱们首先来看一下setState的芳容:java
setState
是组件原型链上的方法,参数为partialState
, callback
,看样子长得仍是比较55开的,参数也很少。提早预告几个参数,_pendingStateQueue
, dirtyComponents
, isBatchingUpdates
, internalInstance
, transaction
react
ReactComponent.prototype.setState = function (partialState, callback) { !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? "development" !== 'production' ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : _prodInvariant('85') : void 0; this.updater.enqueueSetState(this, partialState); // 若是有回调函数,在状态进行更新后执行 if (callback) { this.updater.enqueueCallback(this, callback, 'setState'); } };
enqueueSetState:
git
// updater是存放更新操做方法的一个类 updater.enqueueSetState // mountComponent时把ReactElement做为key,将ReactComponent存入了map中 var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState'); // _pendingStateQueue:待更新队列,若是_pendingStateQueue的值为null,将其赋值为空数组[],并将partialState放入待更新state队列_pendingStateQueue,最后执行enqueueUpdate(internalInstance) var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
enqueueUpdate:
程序员
// getInitialState,componentWillMount,render,componentWillUpdate中setState都不会引发updateComponent // 经过isBatchingUpdates来判断是否处于批量更新的状态 batchingStrategy.isBatchingUpdates!==true ? batchingStrategy.batchedUpdates(enqueueUpdate, component); :dirtyComponents.push(component);
须要注意的是点击事件的处理自己就是在一个大的事务中,isBatchingUpdates已是true了,因此前两次的setState以及最后一次过程的component,都被存入了dirtyComponent中
redux
这个时候问题来了,为什么在当存入dirtyComponent中的时候,什么时候进行更新操做,要知道这个须要至少batchingStrategy的构成以及事务的原理,首先injectBatchingStrategy是经过injectBatchingStrategy进行注入,参数为ReactDefaultBatchingStrategy,具体代码以下:数组
//batchingStrategy批量更新策略 ReactDefaultBatchingStrategy = { isBatchingUpdates: false, batchedUpdates: function(callback, a, b, c, d, e) { // 简单来讲事务有initiation->执行callback->close过程,callback即为enqueueUpdate方法 // transaction经过new ReactDefaultBatchingStrategyTransaction()生成,最后经过一系列调用return TRANSACTION_WRAPPERS return transaction.perform(callback, null, a, b, c, d, e); } }
// 设置两个wrapper,RESET_BATCHED_UPDATES设置isBatchingUpdates,FLUSH_BATCHED_UPDATES会在一个transaction的close阶段运行runBatchedUpdates,从而执行update var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES]; var RESET_BATCHED_UPDATES = { initialize: emptyFunction, close: function () { // 事务批更新处理结束时,将isBatchingUpdates设为了false ReactDefaultBatchingStrategy.isBatchingUpdates = false; } }; var FLUSH_BATCHED_UPDATES = { initialize: emptyFunction, // 关键步骤 close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates) };
下面图片很好地解释了事务的流程,配合TRANSACTION_WRAPPERS的两个对象,perform会依次调用这两个对象内的initialize方法进行初始化操做,而后执行method,最后依次调用这两个对象内的close进行isBatchingUpdates重置以及状态的更新,因为JS的单线程机制,因此每条事务都会依次执行,所以也就有了isBatchingUpdates从false->true->false的过程,这也就意味着partialState不会被存入dirtyComponent中,而是调用batchingStrategy.batchedUpdates(enqueueUpdate, component),进行initialize->enqueueUpdate->close更新state操做babel
perform: function (method, scope, a, b, c, d, e, f) { var errorThrown; var ret; try { this._isInTransaction = true; errorThrown = true; // 先运行全部transactionWrappers中的initialize方法,开始索引为0 this.initializeAll(0); // 再执行perform方法传入的callback,也就是enqueueUpdate ret = method.call(scope, a, b, c, d, e, f); errorThrown = false; } finally { try { if (errorThrown) { // 最后运行wrapper中的close方法,endIndex为0 try { this.closeAll(0); } catch (err) {} } else { // 最后运行wrapper中的close方法 this.closeAll(0); } } finally { this._isInTransaction = false; } } return ret; }
initializeAll: function (startIndex) { var transactionWrappers = this.transactionWrappers; // 遍历全部注册的wrapper for (var i = startIndex; i < transactionWrappers.length; i++) { var wrapper = transactionWrappers[i]; this.wrapperInitData[i] = Transaction.OBSERVED_ERROR; // 调用wrapper的initialize方法 this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null; } }
closeAll: function (startIndex) { var transactionWrappers = this.transactionWrappers; // 遍历全部wrapper for (var i = startIndex; i < transactionWrappers.length; i++) { var wrapper = transactionWrappers[i]; var initData = this.wrapperInitData[i]; errorThrown = true; if (initData !== Transaction.OBSERVED_ERROR && wrapper.close) { // 调用wrapper的close方法 wrapper.close.call(this, initData); } .... } }
var flushBatchedUpdates = function () { // 循环遍历处理完全部dirtyComponents,若是dirtyComponents长度大于1也只执行1次,由于在更新操做的时候会将dirtyComponents设置为null while (dirtyComponents.length || asapEnqueued) { if (dirtyComponents.length) { var transaction = ReactUpdatesFlushTransaction.getPooled(); // close前执行完runBatchedUpdates方法 transaction.perform(runBatchedUpdates, null, transaction); ReactUpdatesFlushTransaction.release(transaction); } if (asapEnqueued) { asapEnqueued = false; var queue = asapCallbackQueue; asapCallbackQueue = CallbackQueue.getPooled(); queue.notifyAll(); CallbackQueue.release(queue); } } };
runBatchedUpdates(){ // 执行updateComponent ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction); }
close前执行完runBatchedUpdates方法, 确定有人和我有同样的疑惑这样在事务结束的时候调用事务内的close再次进行调用flushBatchedUpdates,不是循环调用一直轮回了吗,全局搜索下TRANSACTION_WRAPPERS,发现更新完成之后是一个全新的两个对象,两个close方法,包括设置dirtyComponent长度为0,设置context,callbacks长度为0app
var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING]; var NESTED_UPDATES = { initialize: function () { this.dirtyComponentsLength = dirtyComponents.length; }, close: function () { if (this.dirtyComponentsLength !== dirtyComponents.length) { dirtyComponents.splice(0, this.dirtyComponentsLength); // 关键步骤 flushBatchedUpdates(); } else { dirtyComponents.length = 0; } } }; var UPDATE_QUEUEING = { initialize: function () { this.callbackQueue.reset(); }, close: function () { this.callbackQueue.notifyAll(); } };
执行updateComponent进行状态更新,值得注意的是更新操做内会调用_processPendingState进行原有state的合并以及设置this._pendingStateQueue = null,这也就意味着dirtyComponents进入下一次循环时,执行performUpdateIfNecessary不会再去更新组件
// 执行updateComponent,从而刷新View performUpdateIfNecessary: function (transaction) { if (this._pendingElement != null) { ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context); } // 在setState更新中,其实只会用到第二个 this._pendingStateQueue !== null 的判断,即若是_pendingStateQueue中还存在未处理的state,那就会执行updateComponent完成更新。 else if (this._pendingStateQueue !== null || this._pendingForceUpdate) { // 执行updateComponent,从而刷新View this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context); } else { this._updateBatchNumber = null; } }
_processPendingState: function (props, context) { var inst = this._instance; var queue = this._pendingStateQueue; var replace = this._pendingReplaceState; this._pendingReplaceState = false; this._pendingStateQueue = null; if (!queue) { return inst.state; } if (replace && queue.length === 1) { return queue[0]; } // 获取实例原先状态 var nextState = _assign({}, replace ? queue[0] : inst.state); for (var i = replace ? 1 : 0; i < queue.length; i++) { var partial = queue[i]; // 进行合并操做,若是为partial类型function执行后进行合并 _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial); } return nextState; },
经过上述能够知道,直接修改state,并不会从新触发render,state的更新是一个合并的过程,当使用异步或者callback的方式会使得更新操做以事务的形式进行。所以能够比较容易地解答以前的那个疑问,答案为0,0,3,4。固然若是想实现setState同步更新,大概能够用着三个方法:
// 实现同步办法 // 方法一: incrementCount(){ this.setState((prevState, props) => ({ count: prevState.count + 1 })); this.setState((prevState, props) => ({ count: prevState.count + 1 })); } // 方法二: incrementCount(){ setTimeout(() => { this.setState({ count: prevState.count + 1 }); this.setState({ count: prevState.count + 1 }); }, 0) } // 方法三: incrementCount(){ this.setState({ count: prevState.count + 1 },() => { this.setState({ count: prevState.count + 1 }); }); }
总的来讲setState的过程仍是很优雅的,避免了重复无谓的刷新组件。它的主要流程以下:
enqueueSetState将state放入队列中,并调用enqueueUpdate处理要更新的Component
若是组件当前正处于update事务中,则先将Component存入dirtyComponent中。不然调用batchedUpdates处理,采用事务形式进行批量更新state。
最后结语抒发下程序员的小情怀,生活中总有那么几种方式可以让你快速提升,好比身边有大牛,或者经过学习源代码都是不错的选择,我很幸运这两个条件目前都可以知足到,废话很少说了,时间很晚了洗洗睡了,学习react还依旧长路漫漫,将来得加倍努力才是。