react分析(二)setState”异步”更新

理解

class Home extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            a: 0
        }
    }
    componentDidMount(){
        this.setState({a: this.state.a + 1})
        console.log(this.state.a) // 0
        this.setState({a: this.state.a + 1})
        console.log(this.state.a) // 0
    }
    add = () => {
        this.setState({a: this.state.a + 1})
        console.log(this.state.a) // 1
        this.setState({a: this.state.a + 1})
        console.log(this.state.a) // 1
    };
    add2 = () => {
        setTimeout(()=>{
        this.setState({a: this.state.a + 1})
        console.log(this.state.a) // 2
         this.setState({a: this.state.a + 1})
        console.log(this.state.a) // 3
        })       
    };
    add3 = () => {
        this.setState(p=>({a:p.a+1}))
        console.log(this.state.a) // 1
        this.setState(p=>({a:p.a+1}))
        console.log(this.state.a) // 1
    };

    render() {
        const {form} = this.props;

        return (
            <div>
                {this.state.a}
                <button onClick={this.add}>add</button>
                <button onClick={this.add2}>add2</button>
                <button onClick={this.add3}>add3</button>
            </div>
        )
    }
}

"异步"之所以加引号,是可以理解成异步,但并不是常规意义上的异步,setState更新视图,是有一个批量更新的队列的,这个队列由一个事务transaction,事务可以理解成为要执行函数的fn添加前置和后置,也就是把fn包装下,包装器before和after成对出现,可以有多组包装器,最终执行时为先进先出

function before(){

console.log('before');

}

function after(){

console.log('after');

}

 

* <pre>
*                       wrappers (injected at creation time)
*                                      +        +
*                                      |        |
*                    +-----------------|--------|--------------+
*                    |                 v        |              |
*                    |      +---------------+   |              |
*                    |   +--|    wrapper1   |---|----+         |
*                    |   |  +---------------+   v    |         |
*                    |   |          +-------------+  |         |
*                    |   |     +----|   wrapper2  |--------+   |
*                    |   |     |    +-------------+  |     |   |
*                    |   |     |                     |     |   |
*                    |   v     v                     v     v   | wrapper
*                    | +---+ +---+   +---------+   +---+ +---+ | invariants
* perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
*                    | |   | |   |   |         |   |   | |   | |
*                    | |   | |   |   |         |   |   | |   | |
*                    | |   | |   |   |         |   |   | |   | |
*                    | +---+ +---+   +---------+   +---+ +---+ |
*                    |  initialize                    close    |
*                    +-----------------------------------------+
* </pre>

使用示例

var Transaction = require('./Transaction');

// 我们自己定义的 Transaction
var MyTransaction = function() {
  // do sth.
};

Object.assign(MyTransaction.prototype, Transaction.Mixin, {
  getTransactionWrappers: function() {
    return [{
      initialize: function() {
        console.log('before method perform');
      },
      close: function() {
        console.log('after method perform');
      }
    }];
  };
});

var transaction = new MyTransaction();
var testMethod = function() {
  console.log('test');
}
transaction.perform(testMethod);

// before method perform
// test
// after method perform

即执行perform其实就是执行fn的所有包装器

V:\work\react-source\react-dom\lib\ReactDefaultBatchingStrategy.js
perform: function (method, scope, a, b, c, d, e, f) {
    /* eslint-enable space-before-function-paren */
    !!this.isInTransaction() ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Transaction.perform(...): Cannot initialize a transaction when there is already an outstanding transaction.') : _prodInvariant('27') : void 0;
    var errorThrown;
    var ret;
    try {
      this._isInTransaction = true;
      // Catching errors makes debugging more difficult, so we start with
      // errorThrown set to true before setting it to false after calling
      // close -- if it's still set to true in the finally block, it means
      // one of these calls threw.
      errorThrown = true;
// 执行所有的前置
      this.initializeAll(0);
// 真正执行fn函数
      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.
//最后执行后置
          this.closeAll(0);
        }
      } finally {
        this._isInTransaction = false;
      }
    }
    return ret;
  },

而具体到react中是在\react-source\react-dom\lib\ReactDefaultBatchingStrategy.js声明的

var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function () {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  }
};

var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

function ReactDefaultBatchingStrategyTransaction() {
//reinitializeTransaction是通过_assign把Transaction方法合并到prototype上的,所以才能调用
  this.reinitializeTransaction();
}

_assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
  getTransactionWrappers: function () {
    return TRANSACTION_WRAPPERS;
  }
});

var transaction = new ReactDefaultBatchingStrategyTransaction();

 

断点进入的是

V:\work\react-source\react-dom\lib\ReactUpdateQueue.js  
enqueueSetState: function (publicInstance, partialState) {
    if (process.env.NODE_ENV !== 'production') {
      ReactInstrumentation.debugTool.onSetState();
      process.env.NODE_ENV !== 'production' ? warning(partialState != null, 'setState(...): You passed an undefined or null state object; ' + 'instead, use forceUpdate().') : void 0;
    }
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');

    if (!internalInstance) {
      return;
    }
    var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
// 把{a:1}存入实例_pendingStateQueue待更新state队列中
    queue.push(partialState);
// 根据一定的规则执行更新
    enqueueUpdate(internalInstance);
  },
V:\work\react-source\react-dom\lib\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.)
// 当第一次进入到这里时isBatchingUpdates值就是true,这一步很重要,原因是点击事件开了一个事务把isBatchingUpdates设置为true了,详细看下面的“事件事务”
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
// 所以这里只把component放到dirtyComponents,注意这里的component._pendingStateQueue队列中在上一步已经存入了newState,所以这里直接存component就可以了
  dirtyComponents.push(component);
  if (component._updateBatchNumber == null) {
    component._updateBatchNumber = updateBatchNumber + 1;
  }
}

start   “事件事务”

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`.

点击按钮时会派发事件既而触发一个batchedUpdates,进入到ReactDefaultBatchingStrategy把isBatchingUpdates设置为true,所以后续的setState()会进入批量更新队列

而setTimeout中setState(),这时事务已经close了

 

      ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);

    } finally {

      TopLevelCallbackBookKeeping.release(bookKeeping);

    }

  }

end“事件事务”

 

V:\work\react-source\react-dom\lib\ReactDefaultBatchingStrategy.js
// 执行ReactUpdates.batchedUpdates相当于开启了一个事务,因为把isBatchingUpdates调成true,
// 回到add点击事件,当派发事件执行add回调时,先通过ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);这时isBatchingUpdates=true,然后执行add函数体,函数体里面两个setState()进入到enqueueUpdate因为isBatchingUpdates=true所以把newState保存到dityComponent._pendingStateQueue队列中,最后执行事务的close,close中包括ReactUpdates.NESTED_UPDATES.close.flushBatchedUpdates(),继续挖这个更新函数
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;

    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);
    }
  }
};

 

var flushBatchedUpdates = function () {
  // ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
  // array and perform any updates enqueued by mount-ready handlers (i.e.,
  // componentDidUpdate) but we need to check here too in order to catch
  // updates enqueued by setState callbacks and asap calls.
  while (dirtyComponents.length || asapEnqueued) {
    if (dirtyComponents.length) {
      var transaction = ReactUpdatesFlushTransaction.getPooled();
又能事务形式执行runBatchedUpdates,猜测是想通过initialize获取this.dirtyComponentsLength及后续close设置
      transaction.perform(runBatchedUpdates, null, transaction);
      ReactUpdatesFlushTransaction.release(transaction);
    }

    if (asapEnqueued) {
      asapEnqueued = false;
      var queue = asapCallbackQueue;
      asapCallbackQueue = CallbackQueue.getPooled();
      queue.notifyAll();
      CallbackQueue.release(queue);
    }
  }
};

是后进入V:\work\react-source\react-dom\lib\ReactCompositeComponent.js,receiveComponent后面也是进么updateComponent更新

performUpdateIfNecessary: function (transaction) {
  if (this._pendingElement != null) {
    ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
  } else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
    this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
  } else {
    this._updateBatchNumber = null;
  }
},

updateComponent中有一步_processPendingState是获取最新的newState

  updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {
    var inst = this._instance;
    !(inst != null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Attempted to update component `%s` that has already been unmounted (or failed to mount).', this.getName() || 'ReactCompositeComponent') : _prodInvariant('136', this.getName() || 'ReactCompositeComponent') : void 0;

    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);
      }
    }

    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);
        }
      }
    }

    if (process.env.NODE_ENV !== 'production') {
      process.env.NODE_ENV !== 'production' ? warning(shouldUpdate !== undefined, '%s.shouldComponentUpdate(): Returned undefined instead of a ' + 'boolean value. Make sure to return true or false.', this.getName() || 'ReactCompositeComponent') : void 0;
    }

    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.
      this._currentElement = nextParentElement;
      this._context = nextUnmaskedContext;
      inst.props = nextProps;
      inst.state = nextState;
      inst.context = nextContext;
    }
  },
_processPendingState: function (props, context) {
  var inst = this._instance;
// 这个东东就是最上面enqueueSetState添加的newState队列
//setState({a:this.state.a+1})
//setState({a:this.state.a+1})
//queue = [{a:this.state.a+1}, {a:this.state.a+1}];
  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用法setState({a:this.state.a+1}),setState((prevState, props)=>{a:prevState.a+1})
// 合并newState到旧的state上面,这里就是说明了为什么setState(fn);fn里能拿到上一次最新的state,因为每次合并partial.call都会传入最新的nextState,而{a:this.state.a+1}这种一直是拿的最原始的值
    _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
  }

  return nextState;
},

组件生命周期勾子函数也是在这一步调用的

问题一、

大概willReceive=>_processPendingState=>shouldupdate=>true=>willupdate=>didupdate

回到最开头的 componentDidMount(){ this.setState({a: this.state.a + 1}) console.log(this.state.a) // 0 this.setState({a: this.state.a + 1}) console.log(this.state.a) // 0 }就是组件初始化最两次this.setState({a: this.state.a + 1}),只是把newState存到队列里的,但最后视图中的显示的是1,是什么地方触发了更新呢,是因为

ReactDOM.render=>….._renderNewRootComponent=>

ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);最后执行

最后进入batchedUpdates=》transaction.perform(callback, null, a, b, c, d, e) close更新视图

问题二、

setTimeout中使用setState总是立马更新是因为,加入到了下一次EventLoop,到那时候transaction早就perform了

问题三、

官方说setState()不保证同步,意思有可能出现同步即ReactDefaultBatchingStrategy.isBatchingUpdates = false,目前自己想到的就是setTimeout还有使用非react合成事件,就是自己搞的addEventlistener,根本原因就是脱离了react的事务,理论上可以自己实现。

问题四、

this.setState(p=>({a:p.a+1}))

this.setState(p=>({a:p.a+1}))

console.log(this.state.a)  // 这是拿不到最新的值的

这种方式更新是可以拿到最新的state叠加,但是更新的操作还是存在队列中批量更新的

问题五、

setState不是异步形式,他只是使用了队列的形式合并了多次setState

发表评论

电子邮件地址不会被公开。 必填项已用*标注