深入研究React中setState源码
react作为一门前端框架,虽然只是focus在mvvm中的view部分,但还是实现了view和model的绑定。修改数据的同时,可以实现view的刷新。这大大简化了我们的逻辑,只用关心数据流的变化,同时减少了代码量,使得后期维护也更加方便。这个特性则要归功于setstate()方法。react中利用队列机制来管理state,避免了很多重复的view刷新。下面我们来从源码角度探寻下setstate机制。
1 还是先声明一个组件,从最开始一步步来寻源;
class app extends component { //只在组件重新加载的时候执行一次 constructor(props) { super(props); //.. } //other methods } //reactbaseclasses.js中如下:这里就是setstate函数的来源; //super其实就是下面这个函数 function reactcomponent(props, context, updater) { this.props = props; this.context = context; this.refs = emptyobject; // we initialize the default updater but the real one gets injected by the // renderer. this.updater = updater || reactnoopupdatequeue; } reactcomponent.prototype.setstate = function (partialstate, callback) { this.updater.enqueuesetstate(this, partialstate); if (callback) { this.updater.enqueuecallback(this, callback, 'setstate'); } };
所以主要来看是否传入了updater参数,也就是说何时进行 new 组件;具体的updater参数是怎么传递进来的,以及是那个对象,参见
react源码分析系列文章下面的react中context updater到底是如何传递的
这里直接说结果,updater对象其实就是reactupdatequeue.js 中暴漏出的reactupdatequeue对象;
2 既然找到了setstate之后执行的动作,我们在一步步深入进去
class root extends react.component { constructor(props) { super(props); this.state = { count: 0 }; } componentdidmount() { let me = this; me.setstate({ count: me.state.count + 1 }); console.log(me.state.count); // 打印出0 me.setstate({ count: me.state.count + 1 }); console.log(me.state.count); // 打印出0 settimeout(function(){ me.setstate({ count: me.state.count + 1 }); console.log(me.state.count); // 打印出2 }, 0); settimeout(function(){ me.setstate({ count: me.state.count + 1 }); console.log(me.state.count); // 打印出3 }, 0); } render() { return ( <h1>{this.state.count}</h1> ) } } reactcomponent.prototype.setstate = function (partialstate, callback) { this.updater.enqueuesetstate(this, partialstate); if (callback) { this.updater.enqueuecallback(this, callback, 'setstate'); } };
reactupdatequeue.js
var reactupdates = require('./reactupdates'); function enqueueupdate(internalinstance) { reactupdates.enqueueupdate(internalinstance); }; function getinternalinstancereadyforupdate(publicinstance, callername) { //在reactcompositecomponent.js中有这样一行代码,这就是其来源; // store a reference from the instance back to the internal representation //reactinstancemap.set(inst, this); var internalinstance = reactinstancemap.get(publicinstance); //返回的是在reactcompositecomponent.js中construct函数返回的对象;reactinstance实例对象并不是简单的new 我们写的组件的实例对象,而是经过instantiatereactcomponent.js中reactcompositecomponentwrapper函数包装的对象;详见 创建react组件方式以及源码分析.md return internalinstance; }; var reactupdatequeue = { //。。。。省略其他代码 enqueuecallback: function (publicinstance, callback, callername) { reactupdatequeue.validatecallback(callback, callername); var internalinstance = getinternalinstancereadyforupdate(publicinstance); if (!internalinstance) { return null; } //这里将callback放入组件实例的_pendingcallbacks数组中; if (internalinstance._pendingcallbacks) { internalinstance._pendingcallbacks.push(callback); } else { internalinstance._pendingcallbacks = [callback]; } // todo: the callback here is ignored when setstate is called from // componentwillmount. either fix it or disallow doing so completely in // favor of getinitialstate. alternatively, we can disallow // componentwillmount during server-side rendering. enqueueupdate(internalinstance); }, enqueuesetstate: function (publicinstance, partialstate) { var internalinstance = getinternalinstancereadyforupdate(publicinstance, 'setstate'); if (!internalinstance) { return; } //这里,初始化queue变量,同时初始化 internalinstance._pendingstatequeue = [ ] ; //对于 || 的短路运算还是要多梳理下 //queue数组(模拟队列)中存放着setstate放进来的对象; var queue = internalinstance._pendingstatequeue || (internalinstance._pendingstatequeue = []); //这里将partialstate放入queue数组中,也就是internalinstance._pendingstatequeue 数组中,此时,每次setstate的partialstate,都放进了react组件实例对象上的_pendingstatequeue属性中,成为一个数组; queue.push(partialstate); enqueueupdate(internalinstance); }, }; module.exports = reactupdatequeue;
可以看到enqueuesetstate enqueuecallback 最后都会执行enqueueupdate;
function enqueueupdate(internalinstance) { reactupdates.enqueueupdate(internalinstance); }
reactupdates.js
var dirtycomponents = []; var updatebatchnumber = 0; var asapcallbackqueue = callbackqueue.getpooled(); var asapenqueued = false; //这里声明batchingstrategy为null,后期通过注册给其赋值; var batchingstrategy = null; //这里的component参数是js中reactcompositecomponentwrapper函数包装的后的react组件实例对象; function enqueueupdate(component) { ensureinjected(); //第一次执行setstate的时候,可以进入if语句,遇到里面的return语句,终止执行 //如果不是正处于创建或更新组件阶段,则处理update事务 if (!batchingstrategy.isbatchingupdates) { //batchedupdates就是reactdefaultbatchingstrategy.js中声明的 batchingstrategy.batchedupdates(enqueueupdate, component); return; } //第二次执行setstate的时候,进入不了if语句,将组件放入dirtycomponents //如果正在创建或更新组件,则暂且先不处理update,只是将组件放在dirtycomponents数组中 dirtycomponents.push(component); if (component._updatebatchnumber == null) { component._updatebatchnumber = updatebatchnumber + 1; } }; //enqueueupdate包含了react避免重复render的逻辑。mountcomponent和updatecomponent方法在执行的最开始,会调用到batchedupdates进行批处理更新,此时会将isbatchingupdates设置为true,也就是将状态标记为现在正处于更新阶段了。之后react以事务的方式处理组件update,事务处理完后会调用wrapper.close(), 而transaction_wrappers中包含了reset_batched_updates这个wrapper,故最终会调用reset_batched_updates.close(), 它最终会将isbatchingupdates设置为false。
reactdefaultbatchingstrategy.js
//reset_batched_updates用来管理isbatchingupdates的状态 var reset_batched_updates = { initialize: emptyfunction, close: function () { // 事务批更新处理结束时,将isbatchingupdates设为了false reactdefaultbatchingstrategy.isbatchingupdates = false; } }; //flush_batched_updates会在一个transaction的close阶段运行runbatchedupdates,从而执行update。 //因为close的执行顺序是flush_batched_updates.close ==> 然后reset_batched_updates.close var flush_batched_updates = { initialize: emptyfunction, close: reactupdates.flushbatchedupdates.bind(reactupdates) }; var transaction_wrappers = [flush_batched_updates, reset_batched_updates]; function reactdefaultbatchingstrategytransaction() { this.reinitializetransaction(); } _assign(reactdefaultbatchingstrategytransaction.prototype, transaction, { gettransactionwrappers: function () { return transaction_wrappers; } }); //这个transition就是下面reactdefaultbatchingstrategy对象中使用的transaction变量 var transaction = new reactdefaultbatchingstrategytransaction(); var reactdefaultbatchingstrategy = { isbatchingupdates: false, /** * call the provided function in a context within which calls to `setstate` * and friends are batched such that components aren't updated unnecessarily. */ batchedupdates: function (callback, a, b, c, d, e) { var alreadybatchingupdates = reactdefaultbatchingstrategy.isbatchingupdates; // 批处理最开始时,将isbatchingupdates设为true,表明正在更新 reactdefaultbatchingstrategy.isbatchingupdates = true; // the code is written this way to avoid extra allocations if (alreadybatchingupdates) { return callback(a, b, c, d, e); } else { //transition在上面已经声明; // 以事务的方式处理updates,后面详细分析transaction return transaction.perform(callback, null, a, b, c, d, e); } } }; module.exports = reactdefaultbatchingstrategy;
接下来我们看下react中的事物处理机制到底是如何运行的;
transaction.js
var _prodinvariant = require('./reactprodinvariant'); var invariant = require('fbjs/lib/invariant'); var observed_error = {}; var transactionimpl = { reinitializetransaction: function () { //gettransactionwrappers这个函数reactdefaultbatchingstrategy.js中声明的,上面有;返回一个数组; this.transactionwrappers = this.gettransactionwrappers(); if (this.wrapperinitdata) { this.wrapperinitdata.length = 0; } else { this.wrapperinitdata = []; } this._isintransaction = false; }, _isintransaction: false, gettransactionwrappers: null, isintransaction: function () { return !!this._isintransaction; }, perform: function (method, scope, a, b, c, d, e, f) { var errorthrown; var ret; try { this._isintransaction = true; errorthrown = true; //var transaction_wrappers = [flush_batched_updates, reset_batched_updates]; //1 这里会先执行所有的transaction_wrappers中成员的initialize方法,上面声明的其都是emptyfunction this.initializeall(0); //2 这里其实还是执行的 enqueueupdate 函数 ret = method.call(scope, a, b, c, d, e, f); errorthrown = false; } finally { try { if (errorthrown) { // if `method` throws, prefer to show that stack trace over any thrown // by invoking `closeall`. try { this.closeall(0); } catch (err) {} } else { // since `method` didn't throw, we don't want to silence the exception // here. //3 执行transaction_wrappers对象中成员的所有close方法; this.closeall(0); } } finally { this._isintransaction = false; } } return ret; }, initializeall: function (startindex) { var transactionwrappers = this.transactionwrappers; for (var i = startindex; i < transactionwrappers.length; i++) { var wrapper = transactionwrappers[i]; try { this.wrapperinitdata[i] = observed_error; this.wrapperinitdata[i] = wrapper.initialize ? wrapper.initialize.call(this) : null; } finally { if (this.wrapperinitdata[i] === observed_error) { try { this.initializeall(i + 1); } catch (err) {} } } } }, closeall: function (startindex) { var transactionwrappers = this.transactionwrappers; for (var i = startindex; i < transactionwrappers.length; i++) { var wrapper = transactionwrappers[i]; var initdata = this.wrapperinitdata[i]; var errorthrown; try { errorthrown = true; if (initdata !== observed_error && wrapper.close) { wrapper.close.call(this, initdata); } errorthrown = false; } finally { if (errorthrown) { try { this.closeall(i + 1); } catch (e) {} } } } this.wrapperinitdata.length = 0; } }; module.exports = transactionimpl //3 执行transaction_wrappers对象中成员的所有close方法; var flush_batched_updates = { initialize: emptyfunction, close: reactupdates.flushbatchedupdates.bind(reactupdates) };
接着会执行reactupdates.js中的flushbatchedupdates方法
reactupdates.js中
var flushbatchedupdates = function () { while (dirtycomponents.length || asapenqueued) { if (dirtycomponents.length) { var transaction = reactupdatesflushtransaction.getpooled(); //这里执行runbatchedupdates函数; transaction.perform(runbatchedupdates, null, transaction); reactupdatesflushtransaction.release(transaction); } if (asapenqueued) { asapenqueued = false; var queue = asapcallbackqueue; asapcallbackqueue = callbackqueue.getpooled(); queue.notifyall(); callbackqueue.release(queue); } } }; function runbatchedupdates(transaction) { var len = transaction.dirtycomponentslength; dirtycomponents.sort(mountordercomparator); updatebatchnumber++; for (var i = 0; i < len; i++) { var component = dirtycomponents[i]; var callbacks = component._pendingcallbacks; component._pendingcallbacks = null; var markername; if (reactfeatureflags.logtoplevelrenders) { var namedcomponent = component; // duck type toplevelwrapper. this is probably always true. if (component._currentelement.type.isreacttoplevelwrapper) { namedcomponent = component._renderedcomponent; } markername = 'react update: ' + namedcomponent.getname(); console.time(markername); } //这里才是真正的开始更新组件 reactreconciler.performupdateifnecessary(component, transaction.reconciletransaction, updatebatchnumber); if (markername) { console.timeend(markername); } if (callbacks) { for (var j = 0; j < callbacks.length; j++) { transaction.callbackqueue.enqueue(callbacks[j], component.getpublicinstance()); } } } }
reactreconciler.js中
performupdateifnecessary: function (internalinstance, transaction, updatebatchnumber) { if (internalinstance._updatebatchnumber !== updatebatchnumber) { // the component's enqueued batch number should always be the current // batch or the following one. return; } //这里执行react组件实例对象的更新;internalinstance上的performupdateifnecessary在reactcompositecomponent.js中的; internalinstance.performupdateifnecessary(transaction); if (process.env.node_env !== 'production') { if (internalinstance._debugid !== 0) { reactinstrumentation.debugtool.onupdatecomponent(internalinstance._debugid); } } }
reactcompositecomponent.js
performupdateifnecessary: function (transaction) { if (this._pendingelement != null) { // receivecomponent会最终调用到updatecomponent,从而刷新view reactreconciler.receivecomponent(this, this._pendingelement, transaction, this._context); } else if (this._pendingstatequeue !== null || this._pendingforceupdate) { // 执行updatecomponent,从而刷新view。 this.updatecomponent(transaction, this._currentelement, this._currentelement, this._context, this._context); } else { this._updatebatchnumber = null; } }, //执行更新react组件的props. state。context函数 updatecomponent: function (transaction, prevparentelement, nextparentelement, prevunmaskedcontext, nextunmaskedcontext) { var inst = this._instance; var willreceive = false; var nextcontext; // determine if the context has changed or not if (this._context === nextunmaskedcontext) { nextcontext = inst.context; } else { nextcontext = this._processcontext(nextunmaskedcontext); willreceive = true; } var prevprops = prevparentelement.props; var nextprops = nextparentelement.props; // not a simple state update but a props update if (prevparentelement !== nextparentelement) { willreceive = true; } // an update here will schedule an update but immediately set // _pendingstatequeue which will ensure that any state updates gets // immediately reconciled instead of waiting for the next batch. if (willreceive && inst.componentwillreceiveprops) { if (process.env.node_env !== 'production') { measurelifecycleperf(function () { return inst.componentwillreceiveprops(nextprops, nextcontext); }, this._debugid, 'componentwillreceiveprops'); } else { inst.componentwillreceiveprops(nextprops, nextcontext); } } //这里可以知道为什么setstate可以接受函数,主要就是_processpendingstate函数; //这里仅仅是将每次setstate放入到_pendingstatequeue队列中的值,合并到nextstate,并没有真正的更新state的值;真正更新组件的state的值是在下面; var nextstate = this._processpendingstate(nextprops, nextcontext); var shouldupdate = true; if (!this._pendingforceupdate) { if (inst.shouldcomponentupdate) { if (process.env.node_env !== 'production') { shouldupdate = measurelifecycleperf(function () { return inst.shouldcomponentupdate(nextprops, nextstate, nextcontext); }, this._debugid, 'shouldcomponentupdate'); } else { shouldupdate = inst.shouldcomponentupdate(nextprops, nextstate, nextcontext); } } else { if (this._compositetype === compositetypes.pureclass) { shouldupdate = !shallowequal(prevprops, nextprops) || !shallowequal(inst.state, nextstate); } } } this._updatebatchnumber = null; if (shouldupdate) { this._pendingforceupdate = false; // will set `this.props`, `this.state` and `this.context`. this._performcomponentupdate(nextparentelement, nextprops, nextstate, nextcontext, transaction, nextunmaskedcontext); } else { // if it's determined that a component should not update, we still want // to set props and state but we shortcut the rest of the update. //诺:在这里更新组件的state. props 等值; this._currentelement = nextparentelement; this._context = nextunmaskedcontext; inst.props = nextprops; inst.state = nextstate; inst.context = nextcontext; } }, _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]; //如果是setstate的参数是一个函数,那么该函数接受三个参数,分别是state props context _assign(nextstate, typeof partial === 'function' ? partial.call(inst, nextstate, props, context) : partial); } return nextstate; },
this.state的更新会在_processpendingstate执行完执行。所以两次setstate取到的都是this.state.count最初的值0,这就解释了之前的现象。其实,这也是react为了解决这种前后state依赖但是state又没及时更新的一种方案,因此在使用时大家要根据实际情况来判断该用哪种方式传参。来看个小例子直观感受下
handleclickonlikebutton () { this.setstate({ count: 0 }) // => this.state.count 还是 undefined this.setstate({ count: this.state.count + 1}) // => undefined + 1 = nan this.setstate({ count: this.state.count + 2}) // => nan + 2 = nan } //....vs .... handleclickonlikebutton () { this.setstate((prevstate) => { return { count: 0 } }) this.setstate((prevstate) => { return { count: prevstate.count + 1 } // 上一个 setstate 的返回是 count 为 0,当前返回 1 }) this.setstate((prevstate) => { return { count: prevstate.count + 2 } // 上一个 setstate 的返回是 count 为 1,当前返回 3 }) // 最后的结果是 this.state.count 为 3 } ...
setstate流程还是很复杂的,设计也很精巧,避免了重复无谓的刷新组件。它的主要流程如下
- enqueuesetstate将state放入队列中,并调用enqueueupdate处理要更新的component
- 如果组件当前正处于update事务中,则先将component存入dirtycomponent中。否则调用batchedupdates处理。
- batchedupdates发起一次transaction.perform()事务
- 开始执行事务初始化,运行,结束三个阶段
- 初始化:事务初始化阶段没有注册方法,故无方法要执行
- 运行:执行setsate时传入的callback方法,一般不会传callback参数
- 结束:更新isbatchingupdates为false,并执行flush_batched_updates这个wrapper中的close方法
- flush_batched_updates在close阶段,会循环遍历所有的dirtycomponents,调用updatecomponent刷新组件,并执行它的pendingcallbacks, 也就是setstate中设置的callback。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。