react15.6.2-从源码了解核心流程

3 阅读11分钟

从源码角度,带大家了解,从业务代码开始,是怎样一步一步往底层走的,过程会比较繁琐,本文会忽略具体细节,只保留核心代码

阅读源码前,如果下面内容不熟悉,可以先复习一下,避免造成源码阅读困难

  1. 对象: assign等相关API
  2. 函数: 本质是一个对象,es5如何模拟es6的静态属性、函数,es5如何模拟类
  3. 原型
  4. this
  5. class类:本质是语法糖

可以参考《Javascript高级程序设计》第五版第四章+《Javascript设计模式》第三章,保证不白读

业务代码

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      count: 0
    };
  }

  componentWillMount() {
    console.log('componentWillMount');
  }

  componentDidMount() {
    console.log('componentDidMount');
  }

  handleClick = () => {
    this.setState({
      count: this.state.count + 1
    });
  };

  render() {
    return (
      <div>
        <h1>Hello React 15.6.2</h1>
        <p>{this.state.count}</p>
        <button onClick={this.handleClick}>+1</button>
      </div>
    );
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

初始化渲染

入口

想知道怎么从零开始找入口文件的可以自行看构建文件,这里先直接给出具体路径

  1. React: src/isomorphic/React.js
  2. ReactDOM: src/renderers/dom/ReactDOM.js

React模块化是自己实现的,因此没有相对或者绝对地址这种概念,不知道怎么确定具体文件是哪个,可以搜@providesModule xx,例如require('ReactDOM'),可以搜:@providesModule ReactDOM

由于用到了JSX,因此源码在接收业务代码调用的时候,会先通过babel编译,因此实际上的代码为

const App = React.createElement('App', null)
ReactDOM.render(
  App,
  document.getElementById('root')
);

React.createElement

文件映射

ReactElement: src/isomorphic/classic/element/ReactElement.js
  1. React.js
var React = {
    Component: ReactBaseClasses.Component,
    PureComponent: ReactBaseClasses.PureComponent,
    createElement: ReactElement.createElement
}

2.ReactElement

var ReactElement = function(){
    return element
}
ReactElement.createElement = function(){
    return ReactElement()
}

ReactDom.render

文件映射

ReactMount: src/renderers/dom/client/ReactMount.js
ReactUpdates: src/renderers/shared/stack/reconciler/ReactUpdates.js
instantiateReactComponent: src/renderers/shared/stack/reconciler/instantiateReactComponent.js
  1. ReactDom
ReactDefaultInjection.inject();//依赖注入,后面阅读ReactUpdates有用
var ReactDOM = {
    render: ReactMount.render,
}
  1. ReactMount

内部顺序: ReactMount.render -> ReactMount._renderSubtreeIntoContainer -> ReactMount._renderNewRootComponent -> ReactUpdates.batchedUpdates

var ReactMount = {
  TopLevelWrapper,
  render: function(nextElement, container, callback) {
    return ReactMount._renderSubtreeIntoContainer(
      null,
      nextElement,
      container,
      callback,
    );
  },
  _renderSubtreeIntoContainer: function(
    parentComponent,
    nextElement,
    container,
    callback,
  ) {
    var nextWrappedElement = React.createElement(TopLevelWrapper, {
      child: nextElement,
    });
    var component = ReactMount._renderNewRootComponent(
      nextWrappedElement,//<App/>
      container,//document.getElementById('root')
      shouldReuseMarkup,
      nextContext,
    )._renderedComponent.getPublicInstance();

    return component;
  },
  _renderNewRootComponent: function(
    nextElement,
    container,
    shouldReuseMarkup,
    context,
  ) {
    var componentInstance = instantiateReactComponent(nextElement, false);

    // The initial render is synchronous but any updates that happen during
    // rendering, in componentWillMount or componentDidMount, will be batched
    // according to the current batching strategy.

    ReactUpdates.batchedUpdates(
      batchedMountComponentIntoNode,
      componentInstance,
      container,
      shouldReuseMarkup,
      context,
    );

    return componentInstance;
  },
}

ReactUpdates.batchedUpdates是做了一个开启批量更新环境,注释也说了,如果componentWillMount或者componentDidMount内部出现重新setState,会进入批量处理

  1. ReactUpdates
var batchingStrategy = null;
function batchedUpdates(callback, a, b, c, d, e) {
  ensureInjected();
  return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}

var ReactUpdatesInjection = {
  injectBatchingStrategy: function(_batchingStrategy) {
    batchingStrategy = _batchingStrategy;
}
var ReactUpdates = {
  batchedUpdates: batchedUpdates,
}

阅读上面代码会发现有ReactUpdatesInjection.injectBatchingStrategy,只有调用ReactUpdatesInjection.injectBatchingStrategy,才有:batchingStrategy.batchedUpdates,之前我在ReacDom有写到依赖注入ReactDefaultInjection.inject()

ReactDefaultInjection

function inject(){
  ReactInjection.Updates.injectBatchingStrategy(ReactDefaultBatchingStrategy);
}

ReactInjection

var ReactInjection = {
  Updates: ReactUpdates.injection,
};

ReactDefaultBatchingStrategy

function ReactDefaultBatchingStrategyTransaction() {
  this.reinitializeTransaction();
}
Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
  getTransactionWrappers: function() {
    return TRANSACTION_WRAPPERS;
  },
});
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;

    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(callback, null, a, b, c, d, e); Transaction

var TransactionImpl = {
    perform: function(method,scope,a,b,c,d,e,f){
        try{
            ret = method.call(scope, a, b, c, d, e, f);
        }finnally{
            this.closeAll(0);
        }
    }
}
module.exports = TransactionImpl;

method.call(scope, a, b, c, d, e, f);也就是

batchedMountComponentIntoNode.call(  
null,  
componentInstance,  
container,  
shouldReuseMarkup,  
context,  
undefined  
)

4.继续回到ReactMount

流程概述:batchedMountComponentIntoNode -> mountComponentIntoNode ->

function mountComponentIntoNode(
  wrapperInstance,
  container,
  transaction,
  shouldReuseMarkup,
  context,
) {
  var markup = ReactReconciler.mountComponent(
    wrapperInstance,
    transaction,
    null,
    ReactDOMContainerInfo(wrapperInstance, container),
    context,
    0 /* parentDebugID */,
  );
  ReactMount._mountImageIntoNode(
    markup,
    container,
    wrapperInstance,
    shouldReuseMarkup,
    transaction,
  );
}
function batchedMountComponentIntoNode(
  componentInstance,
  container,
  shouldReuseMarkup,
  context,
) {
  var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
    /* useCreateElement */
    !shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement,
  );
  transaction.perform(
    mountComponentIntoNode,
    null,
    componentInstance,
    container,
    transaction,
    shouldReuseMarkup,
    context,
  );
  ReactUpdates.ReactReconcileTransaction.release(transaction);
}

ReactMount._mountImageIntoNode是把markup插入到#root,因此看ReactReconciler.mountComponent

看ReactReconciler

var ReactReconciler = {
  mountComponent: function(
    internalInstance,
    transaction,
    hostParent,
    hostContainerInfo,
    context,
    parentDebugID, // 0 in production and for roots
  ) {
      var markup = internalInstance.mountComponent(
      transaction,
      hostParent,
      hostContainerInfo,
      context,
      parentDebugID,
    );
    return markup;
  }
}

这里的internalInstance从ReactMount来的 ReactMount

var componentInstance = instantiateReactComponent(nextElement, false);
  1. instantiateReactComponent
var ReactCompositeComponentWrapper = function(element) {
  this.construct(element);
};
function instantiateReactComponent(node, shouldHaveDebugID) {
    var instance = new ReactCompositeComponentWrapper(element);
    return instance
}

Object.assign(
  ReactCompositeComponentWrapper.prototype,
  ReactCompositeComponent,
  {
    _instantiateReactComponent: instantiateReactComponent,
  },
);

而internalInstance.mountComponent,mountComponent会从ReactCompositeComponentWrapper的原型找,也就是ReactCompositeComponent

  1. ReactCompositeComponent
function shouldConstruct(Component) {
  return !!(Component.prototype && Component.prototype.isReactComponent);
}
var ReactCompositeComponent = {
  mountComponent: function(
    transaction,
    hostParent,
    hostContainerInfo,
    context,
  ) {
    var updateQueue = transaction.getUpdateQueue();
    
    // Initialize the public class
    var doConstruct = shouldConstruct(Component);
    var inst = this._constructComponent(
      doConstruct,
      publicProps,
      publicContext,
      updateQueue,
    );
    var renderedElement;

    // Support functional components
    if (!doConstruct && (inst == null || inst.render == null)) {
      renderedElement = inst;
      warnIfInvalidElement(Component, renderedElement);
      inst = new StatelessComponent(Component);//对函数进行包装,抹平class写法和函数写法
      this._compositeType = CompositeTypes.StatelessFunctional;
    } else {
      if (isPureComponent(Component)) {
        this._compositeType = CompositeTypes.PureClass;
      } else {
        this._compositeType = CompositeTypes.ImpureClass;
      }
    }



    // These should be set up in the constructor, but as a convenience for
    // simpler class abstractions, we set them up after the fact.
    inst.props = publicProps;
    inst.context = publicContext;
    inst.refs = emptyObject;
    inst.updater = updateQueue;

    this._instance = inst;

    // Store a reference from the instance back to the internal representation
    ReactInstanceMap.set(inst, this);

   


    var initialState = inst.state;
    if (initialState === undefined) {
      inst.state = initialState = null;
    }


    this._pendingStateQueue = null;
    this._pendingReplaceState = false;
    this._pendingForceUpdate = false;

    var markup = this.performInitialMount(
        renderedElement,
        hostParent,
        hostContainerInfo,
        transaction,
        context,
      );

    if (inst.componentDidMount) {
        transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
    }

    return markup;
  },
  _constructComponent: function(
    doConstruct,
    publicProps,
    publicContext,
    updateQueue,
  ) {
      return this._constructComponentWithoutOwner(
        doConstruct,
        publicProps,
        publicContext,
        updateQueue,
      );
  },
  _constructComponentWithoutOwner: function(
    doConstruct,
    publicProps,
    publicContext,
    updateQueue,
  ) {
    var Component = this._currentElement.type;

    if (doConstruct) {//class写法
      return new Component(publicProps, publicContext, updateQueue);//返回new App()
    }

    return Component(publicProps, publicContext, updateQueue);//返回函数执行App()
  },
  performInitialMount: function(
    renderedElement,
    hostParent,
    hostContainerInfo,
    transaction,
    context,
  ) {
    var inst = this._instance;

    var debugID = 0;


    if (inst.componentWillMount) {
      inst.componentWillMount();
      // When mounting, calls to `setState` by `componentWillMount` will set
      // `this._pendingStateQueue` without triggering a re-render.
      if (this._pendingStateQueue) {
        inst.state = this._processPendingState(inst.props, inst.context);
      }
    }

    // If not a stateless component, we now render
    if (renderedElement === undefined) {
      renderedElement = this._renderValidatedComponent();
    }

    var nodeType = ReactNodeTypes.getType(renderedElement);
    this._renderedNodeType = nodeType;
    var child = this._instantiateReactComponent(
      renderedElement,
      nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */,
    );
    this._renderedComponent = child;

    var markup = ReactReconciler.mountComponent(
      child,
      transaction,
      hostParent,
      hostContainerInfo,
      this._processChildContext(context),
      debugID,
    );



    return markup;
  },
  _renderValidatedComponent: function() {
    var renderedElement;
    renderedElement = this._renderValidatedComponentWithoutOwnerOrContext();

    return renderedElement;
  },
  _renderValidatedComponentWithoutOwnerOrContext: function() {
    var inst = this._instance;
    var renderedElement;

    renderedElement = inst.render();



    return renderedElement;
  },
}

结论

挂载的核心流程

  1. 创建组件实例

    • class组件:new App(...)
    • 函数组件:执行 App(props)
    • 函数组件会被包装成 StatelessComponent,方便统一处理
  2. 执行 componentWillMount

  3. 执行 render,得到新的 ReactElement

  4. 对 render 返回的 ReactElement 继续 instantiateReactComponent, 然后递归 mount 子组件

  5. 生成 markup,并插入真实 DOM

  6. 将 componentDidMount 放入 ReactMountReady 队列

  7. 整棵树挂载完成后,统一执行 componentDidMount

更新状态

入口

this.setState({ count: this.state.count + 1 });

setState会通过原型找到ReactCopoment的实例方法 文件路径

ReactBaseClasses: src/isomorphic/modern/class/ReactBaseClasses.js
ReactReconcileTransaction:src/renderers/dom/client/ReactReconcileTransaction.js
Transaction: src/renderers/shared/utils/Transaction.js
CallbackQueue: src/renderers/shared/utils/CallbackQueue.js
ReactReconciler: src/renderers/shared/stack/reconciler/ReactReconciler.js
ReactCompositeComponent: src/renderers/shared/stack/reconciler/ReactCompositeComponent.js

setState

调用过程

this.updater.enqueueSetState(this, partialState);

↓ ReactUpdates.enqueueUpdate(internalInstance);

ReactBaseClasses

function ReactComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;//兜底方法而已
}
ReactComponent.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

this.updater.enqueueSetState,要找到ReactCompositeComponent的

var updateQueue = transaction.getUpdateQueue();
inst.updater = updateQueue;

上面有一段

var transaction = ReactUpdates.ReactReconcileTransaction.getPooled( /* useCreateElement */ !shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement, );
transaction.perform( mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context, );

所以transaction在ReactReconcileTransaction的getPooled

ReactReconcileTransaction

function ReactReconcileTransaction(useCreateElement: boolean) {
  this.reinitializeTransaction();
  // Only server-side rendering really needs this option (see
  // `ReactServerRendering`), but server-side uses
  // `ReactServerRenderingTransaction` instead. This option is here so that it's
  // accessible and defaults to false when `ReactDOMComponent` and
  // `ReactDOMTextComponent` checks it in `mountComponent`.`
  this.renderToStaticMarkup = false;
  this.reactMountReady = CallbackQueue.getPooled(null);
  this.useCreateElement = useCreateElement;
}
var Mixin = {
 /**
   * @return {object} The queue to collect `onDOMReady` callbacks with.
   */
  getReactMountReady: function() {
    return this.reactMountReady;
  },

  /**
   * @return {object} The queue to collect React async events.
   */
  getUpdateQueue: function() {
    return ReactUpdateQueue;
  },
}
Object.assign(ReactReconcileTransaction.prototype, Transaction, Mixin);

最终找到ReactUpdateQueue

ReactUpdateQueue

function enqueueUpdate(internalInstance) {
  ReactUpdates.enqueueUpdate(internalInstance);
}
var ReactUpdateQueue = {
  enqueueCallback: function(publicInstance, callback, callerName) {
    ReactUpdateQueue.validateCallback(callback, callerName);
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);



    if (internalInstance._pendingCallbacks) {
      internalInstance._pendingCallbacks.push(callback);
    } else {
      internalInstance._pendingCallbacks = [callback];
    }

    enqueueUpdate(internalInstance);
  },
    enqueueSetState: function(publicInstance, partialState) {


    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'setState',
    );

    if (!internalInstance) {
      return;
    }

    var queue =
      internalInstance._pendingStateQueue ||
      (internalInstance._pendingStateQueue = []);
    queue.push(partialState);

    enqueueUpdate(internalInstance);
  },
}

ReactUpdates

function enqueueUpdate(component) {

  if (!batchingStrategy.isBatchingUpdates) {//在初始化渲染我们已经看到开启,因此不会走这里,除非是异步环境
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }

  dirtyComponents.push(component);
  if (component._updateBatchNumber == null) {
    component._updateBatchNumber = updateBatchNumber + 1;
  }
}
var ReactUpdates = {
  batchedUpdates: batchedUpdates,
  enqueueUpdate: enqueueUpdate,
};

_pendingStateQueue保存要更新的状态,_pendingCallbacks保存更新回调,最终这个需要更新的组件放到:dirtyComponents里面

this.setState开始,我们从源码已经找完所有流程了,因此必须回到初始化流程,在初始化流程,有对dirtyComponents进行相关处理,我们之前为了找ReactDom.render主流程,做了一些忽略,现在我们需要从batchedUpdates继续看下去

ReactMount

// The initial render is synchronous but any updates that happen during 
// rendering, in componentWillMount or componentDidMount, will be batched 
// according to the current batching strategy.
ReactUpdates.batchedUpdates(  
    batchedMountComponentIntoNode,  
    componentInstance,  
    container,  
    shouldReuseMarkup,  
    context,  
);

回顾一下之前这里的注释,初始化render的时候会先同步操作,如果在渲染过程、componentWillMount、componentDidMount遇到setState,会开启批量 而dirtyComponents,会等同步操作也就是最后插入dom后进行处理,因此我们需要回到这里继续往下看

ReactUpdates.batchedUpdates

ReactUpdates.batchedUpdates//ReactMount
↓  
batchingStrategy.batchedUpdates(callback, a, b, c, d, e)//ReactUpdates
↓ 
transaction.perform(callback, null, a, b, c, d, e)//ReactDefaultBatchingStrategy
↓ 
transaction.perform//ReactMountthis.closeAll(0);//Transaction(fn:perform)
↓ 
wrapper.close.call(this, initData);Transaction(fn:closeAll)
↓ 
ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)//ReactDefaultBatchingStrategy
↓ 
transaction.perform(runBatchedUpdates, null, transaction);//ReactUpdatesReactReconciler.performUpdateIfNecessary(//ReactUpdates
   component,
   transaction.reconcileTransaction,
   updateBatchNumber,
);
↓ 
internalInstance.performUpdateIfNecessary(transaction);//ReactReconcilerthis.updateComponent(
     transaction,
     this._currentElement,
     this._currentElement,
     this._context,
     this._context,
   );//ReactCompositeComponent.performUpdateIfNecessarythis._performComponentUpdate(
     nextParentElement,
     nextProps,
     nextState,
     nextContext,
     transaction,
     nextUnmaskedContext,
  );//ReactCompositeComponent.updateComponentthis._updateRenderedComponent(transaction, unmaskedContext);//ReactCompositeComponent

如果type和key相同

ReactReconciler.receiveComponent( prevComponentInstance, nextRenderedElement, transaction, this._processChildContext(context), );//ReactCompositeComponent
↓ 
internalInstance.receiveComponent(nextElement, transaction, context);//ReactReconcilerthis.updateComponent(//ReactCompositeComponent
      transaction,
      prevElement,
      nextElement,
      prevContext,
      nextContext,
    );

ReactUpdates

function batchedUpdates(callback, a, b, c, d, e) {
  return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);//batchingStrategy是注入进来的ReactDefaultBatchingStrategy,可以看render流程
}
var ReactUpdates = {
  batchedUpdates: batchedUpdates,
}

ReactDefaultBatchingStrategy

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];
Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
  getTransactionWrappers: function() {
    return TRANSACTION_WRAPPERS;
  },
});
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;

    ReactDefaultBatchingStrategy.isBatchingUpdates = true;
    
    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
      return transaction.perform(callback, null, a, b, c, d, e);//走的这里
    }
  },
};

无论是第一次render,还是用户操作更新,第一次都是false,虽然这里ReactDefaultBatchingStrategy.isBatchingUpdates=truetransaction.perform之后,处理dirtyComponents后,依然会ReactDefaultBatchingStrategy.isBatchingUpdates=false,因此如果我们想了解dirtyComponents是怎么处理的,走transaction.perform(callback, null, a, b, c, d, e)

Transaction

var TransactionImpl = {
  perform: function<
    A,
    B,
    C,
    D,
    E,
    F,
    G,
    T: (a: A, b: B, c: C, d: D, e: E, f: F) => G,
  >(method: T, scope: any, a: A, b: B, c: C, d: D, e: E, f: F): G {
    try {

      ret = method.call(scope, a, b, c, d, e, f);
      errorThrown = false;
    } finally {
      try {
        if (errorThrown) {
          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;
  },
  closeAll: function(startIndex: number): void {
    invariant(
      this.isInTransaction(),
      'Transaction.closeAll(): Cannot close transaction when none are open.',
    );
    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) {
          // The closer for wrapper i threw an error; close the remaining
          // wrappers but silence any exceptions from them to ensure that the
          // first error is the one to bubble up.
          try {
            this.closeAll(i + 1);
          } catch (e) {}
        }
      }
    }
    this.wrapperInitData.length = 0;
  },
}

perform里面会执行mount,插入,这是render的主流程,而结束后会循环执行close,因此这里的wrapper实际上是TRANSACTION_WRAPPERS,只需要看ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)就行

ReactUpdates

function runBatchedUpdates(transaction) {
  var len = transaction.dirtyComponentsLength;

  // Since reconciling a component higher in the owner hierarchy usually (not
  // always -- see shouldComponentUpdate()) will reconcile children, reconcile
  // them before their children by sorting the array.
  dirtyComponents.sort(mountOrderComparator);

  // Any updates enqueued while reconciling must be performed after this entire
  // batch. Otherwise, if dirtyComponents is [A, B] where A has children B and
  // C, B could update twice in a single batch if C's render enqueues an update
  // to B (since B would have already updated, we should skip it, and the only
  // way we can know to do so is by checking the batch counter).
  updateBatchNumber++;

  for (var i = 0; i < len; i++) {
    // If a component is unmounted before pending changes apply, it will still
    // be here, but we assume that it has cleared its _pendingCallbacks and
    // that performUpdateIfNecessary is a noop.
    var component = dirtyComponents[i];

    // If performUpdateIfNecessary happens to enqueue any new updates, we
    // shouldn't execute the callbacks until the next render happens, so
    // stash the callbacks first
    var callbacks = component._pendingCallbacks;
    component._pendingCallbacks = null;



    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(),
        );
      }
    }
  }
}
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();
      transaction.perform(runBatchedUpdates, null, transaction);
      ReactUpdatesFlushTransaction.release(transaction);
    }
  }
};
var ReactUpdates = {
  enqueueUpdate: enqueueUpdate,
  flushBatchedUpdates: flushBatchedUpdates,
  injection: ReactUpdatesInjection,
}

flushBatchedUpdates里会做循环检查,因为一次setState的回调里,有可能再次setState,我们可以忽略transaction.perform里面的close,那是做dirtyComponents的清除以及处理回调里面setState,不是更新主流程,感兴趣可以自行查看

ReactReconciler

var ReactReconciler = {
  performUpdateIfNecessary: function(
    internalInstance,
    transaction,
    updateBatchNumber,
  ) {
    internalInstance.performUpdateIfNecessary(transaction);
  }
}

performUpdateIfNecessary是通过internalInstance去调用的,需要一步一步往回找,这里忽略过程,最终在ReactCompositeComponent能找到

ReactCompositeComponent

var ReactCompositeComponent = {
  performUpdateIfNecessary: function(transaction) {
    if (this._pendingElement != null) {//第二次执行ReactDom.render才会有值
      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: 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) {
      inst.componentWillReceiveProps(nextProps, nextContext);
    }

    var nextState = this._processPendingState(nextProps, nextContext);
    var shouldUpdate = true;

    if (!this._pendingForceUpdate) {
      if (inst.shouldComponentUpdate) {
         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.
      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;
    }

    var nextState = Object.assign({}, inst.state);
    for (var i = 0; i < queue.length; i++) {
      var partial = queue[i];
      Object.assign(
        nextState,
        typeof partial === 'function'
          ? partial.call(inst, nextState, props, context)
          : partial,
      );
    }

    return nextState;
  },
  _performComponentUpdate: function(
    nextElement,
    nextProps,
    nextState,
    nextContext,
    transaction,
    unmaskedContext,
  ) {
    var inst = this._instance;

    var hasComponentDidUpdate = Boolean(inst.componentDidUpdate);
    var prevProps;
    var prevState;
    var prevContext;
    if (hasComponentDidUpdate) {
      prevProps = inst.props;
      prevState = inst.state;
      prevContext = inst.context;
    }

    if (inst.componentWillUpdate) {
      inst.componentWillUpdate(nextProps, nextState, nextContext);
    }

    this._currentElement = nextElement;
    this._context = unmaskedContext;
    inst.props = nextProps;
    inst.state = nextState;
    inst.context = nextContext;

    this._updateRenderedComponent(transaction, unmaskedContext);

    if (hasComponentDidUpdate) {
       transaction
          .getReactMountReady()
          .enqueue(
            inst.componentDidUpdate.bind(
              inst,
              prevProps,
              prevState,
              prevContext,
            ),
            inst,
          );
    }
  },
  _updateRenderedComponent: function(transaction, context) {
    var prevComponentInstance = this._renderedComponent;
    var prevRenderedElement = prevComponentInstance._currentElement;
    var nextRenderedElement = this._renderValidatedComponent();

    var debugID = 0;


    if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
      ReactReconciler.receiveComponent(
        prevComponentInstance,
        nextRenderedElement,
        transaction,
        this._processChildContext(context),
      );
    } else {
      var oldHostNode = ReactReconciler.getHostNode(prevComponentInstance);
      ReactReconciler.unmountComponent(prevComponentInstance, false);

      var nodeType = ReactNodeTypes.getType(nextRenderedElement);
      this._renderedNodeType = nodeType;
      var child = this._instantiateReactComponent(
        nextRenderedElement,
        nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */,
      );
      this._renderedComponent = child;

      var nextMarkup = ReactReconciler.mountComponent(
        child,
        transaction,
        this._hostParent,
        this._hostContainerInfo,
        this._processChildContext(context),
        debugID,
      );



      this._replaceNodeWithMarkup(
        oldHostNode,
        nextMarkup,
        prevComponentInstance,
      );
    }
  },
}
  1. 如果接收到新的 element,且定义了 componentWillReceiveProps,则执行(因为setState导致的更新,element不变,递归后续则会)
  2. 批量更新state的时候,会从state队列:pendingState遍历,执行Object.assign,这样最终结果就是:如果是普通值,因为一次次的覆盖,导致相同属性只会更新最后一个值;如果是函数,由于有有形参和返回,导致每次都能从之前的结果作为形参,参与计算,得到最新结果,通过函数作为返回值,做到实时更新而不是只更新最后一个值(如果后续递归的组件,该实例自己的 pendingStateQueue 不为空,才会真正合并)
  3. 默认shouldUpdade=true;shouldUpdade计算:如果组件定义shouldComponentUpdate,则执行shouldComponentUpdate作为返回值;如果没定义shouldComponentUpdate,且为purecomponent,则shouldUpdade=浅比较props跟state是否相同
  4. 如果shouldUpdade=false,只更新props、context、state,到此为止,否则进入后续的更新渲染(第5步)
  5. componentWillUpdate
6(1)对比新旧虚拟节点,如果type和key相同

ReactReconciler

var ReactReconciler = {
  receiveComponent: function(
    internalInstance,
    nextElement,
    transaction,
    context,
  ) {
    internalInstance.receiveComponent(nextElement, transaction, context);
  },
}

ReactCompositeComponent

var ReactCompositeComponent = {
  receiveComponent: function(nextElement, transaction, nextContext) {
    var prevElement = this._currentElement;
    var prevContext = this._context;

    this._pendingElement = null;

    this.updateComponent(
      transaction,
      prevElement,
      nextElement,
      prevContext,
      nextContext,
    );
  },
  
}

递归子组件,重复执行1-5

6(2)对比新旧虚拟节点,如果type或key不同

6.2.1. 如果有componentWillUnmount,旧的虚拟节点:执行componentWillUnmount,递归虚拟树执行componentWillUnmount,清空队列,断开引用,等待回收

ReactCompositeComponent

ReactReconciler.unmountComponent(prevComponentInstance, false);
 unmountComponent: function(safely) {
    if (!this._renderedComponent) {
      return;
    }

    var inst = this._instance;

    if (inst.componentWillUnmount && !inst._calledComponentWillUnmount) {
      inst._calledComponentWillUnmount = true;

     inst.componentWillUnmount();
    }

    if (this._renderedComponent) {
      ReactReconciler.unmountComponent(this._renderedComponent, safely);
      this._renderedNodeType = null;
      this._renderedComponent = null;
      this._instance = null;
    }

    // Reset pending fields
    // Even if this component is scheduled for another update in ReactUpdates,
    // it would still be ignored because these fields are reset.
    this._pendingStateQueue = null;
    this._pendingReplaceState = false;
    this._pendingForceUpdate = false;
    this._pendingCallbacks = null;
    this._pendingElement = null;

    // These fields do not really need to be reset since this object is no
    // longer accessible.
    this._context = null;
    this._rootNodeID = 0;
    this._topLevelWrapper = null;


    ReactInstanceMap.remove(inst);


  },

6.2.2. 现在同一个节点的子组件,重新创建子组件实例(初始化渲染那一套,constructor、WillMount、render、mount)

  1. 如果有componentDidUpdate,则进入队列后执行

结论

更新的核心流程

  1. 找到当前组件对应的 internalInstance

  2. 把 partialState 放进:
    internalInstance._pendingStateQueue

  3. 如果有 callback,把 callback 放进:
    internalInstance._pendingCallbacks

  4. 把 internalInstance 放进:
    dirtyComponents

  5. 等当前 batch 结束后:
    flushBatchedUpdates()

    处理 dirtyComponents

    合并 _pendingStateQueue

    得到 nextState

    render

    diff

    更新 DOM

    执行 _pendingCallbacks