15

法国队时隔20年夺得了世界杯冠军,这个令人兴奋的消息到现在还在我内心不能平息,矫情过后开始着手分析一波,当然了分析的比较拙劣,希望能给学习react的人送去一点启发和希望,写的有所纰漏,不足之处还希望知识储备是我几倍的大牛们们指出。

正如大家一致公认的react是以数据为核心的,因此说到组件更新的时候我下意识的会想到当状态变化的时候会进行组件更新。除了通过redux进行状态管理从而进行组件更新外还有像我这种菜鸡通过
setState进行状态修改,关于setState,相信有不少人一开始和我有一样的疑惑,为何不能通过普通的this.state来进行状态更新,在开始前首先思考个问题,下面的打印结果分别是多少

 <!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的芳容:

setState是组件原型链上的方法,参数为partialState, callback,看样子长得还是比较55开的,参数也不多。提前预告几个参数,_pendingStateQueue, dirtyComponents, isBatchingUpdates, internalInstance, transaction

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:

// 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中

图片描述

这个时候问题来了,为何在当存入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操作

图片描述

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长度为0

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还依旧长路漫漫,未来得加倍努力才是。

参考:
深入react技术栈
CSDN
简书
掘金
知乎
gitbook


ZHONGJIAFENG7
165 声望5 粉丝

React追求者