React v16-alpha 从virtual dom 到 dom 源码简读

一、物料准备

1.克隆react源码, github 地址:https://github.com/facebook/react.git

2.安装gulp  

3.在react源码根目录下:

   $npm install

   $gulp default

   (建议使用node 6.0+)

  gulp将文件处理在根目录下的build文件夹中,打开build查看react的源码,结构清晰,引用路径明了

二、从生成 virtual dom 开始

react 生成一个组件有多种写法:

es 5下:var Cp=React.createClass({...})

es 6下:class Cp extends React.Component{...}

下面打开./build/node_modules/react/lib 文件夹,找到React.js 可以看到如下关键代码:

var React = {

  // Modern

  Children: {
    map: ReactChildren.map,
    forEach: ReactChildren.forEach,
    count: ReactChildren.count,
    toArray: ReactChildren.toArray,
    only: onlyChild
  },

  Component: ReactComponent,
  PureComponent: ReactPureComponent,

  createElement: createElement,
  cloneElement: cloneElement,
  isValidElement: ReactElement.isValidElement,

  // Classic

  PropTypes: ReactPropTypes,
  createClass: ReactClass.createClass,
  createFactory: createFactory,
  createMixin: function (mixin) {
    // Currently a noop. Will be used to validate and trace mixins.
    return mixin;
  },

  // This looks DOM specific but these are actually isomorphic helpers
  // since they are just generating DOM strings.
  DOM: ReactDOMFactories,

  version: ReactVersion,

  // Deprecated hook for JSX spread, don't use this for anything.
  __spread: __spread
};

由此得知:React.createClass => ReactClass.createClass    

              React.component => ReactComponent

1.ReactClass.createClass

下面还是在当前的目录下寻找ReactClass.js文件,查看到如下关键代码段:

var ReactClass = {
    createClass: function (spec) {
    var Constructor = function (props, context, updater) {
       //如果不是生产环境 输出信息类警告 目前忽略
      if (process.env.NODE_ENV !== 'production') {...}
       // 自动绑定相关方法 目前忽略
      if (this.__reactAutoBindPairs.length) {...}
       //为组件绑定props  context refs updater属性
      this.props = props;
      this.context = context;
      this.refs = emptyObject;
      this.updater = updater || ReactNoopUpdateQueue;
      //初始组件state为null
      this.state = null;
      //如果有getInitialState则执行
      var initialState = this.getInitialState ? this.getInitialState() : null;
      //在非生产环境下为配合mock 设置initialState为null 目前忽略
      if (process.env.NODE_ENV !== 'production') {...}
      //其他情况下的兼容性处理,目前忽略
      ...
      //将初始化的state赋值给组件state
      this.state = initialState;
    };
    //设置Constructor的原型
    Constructor.prototype = new ReactClassComponent();
    Constructor.prototype.constructor = Constructor;
    Constructor.prototype.__reactAutoBindPairs = [];
    //合并研发同学写入的createClass({中的东西})
    mixSpecIntoComponent(Constructor, spec);
    //如果存在getDefaultProps则执行
    if (Constructor.getDefaultProps) {
      Constructor.defaultProps = Constructor.getDefaultProps();
    }
   ...省略一些无关主逻辑的操作

    return Constructor;
  }

};
View Code

通过上面的代码我们可以知道:

a.createClass生成一个constructor并return它,这个constructor就是我们的组件
b.这个constructor继承自ReactClassComponent
c.了解react组件声明周期的同学应该知道React组件在整个生命周期中getDefaultProps只执行一次了吧
d.研发组件的同学在createClass({中写的东西})是通过mixSpecIntoComponent方法融合进constructor中的

下面请看mixSpecIntoComponent代码

function mixSpecIntoComponent(Constructor, spec) {
  if (!spec) {
    //当spec不存在时 即研发同学没有写createClass中的东西
    ...省略警告文本
    return;
  }
  ...省略spec类型容错处理
  var proto = Constructor.prototype;
  var autoBindPairs = proto.__reactAutoBindPairs;
  //关于mixins的相关处理 其实就是递归调用mixSpecIntoComponent
  //MIXINS_KEY="mixins"
  if (spec.hasOwnProperty(MIXINS_KEY)) {
    RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins);
  }
  //循环遍历spec
  for (var name in spec) {
    ...省略容错处理
    var property = spec[name];
    var isAlreadyDefined = proto.hasOwnProperty(name);
    //覆写constructor.prototype中的方法
    validateMethodOverride(isAlreadyDefined, name);
    //对特定的属性名做特殊处理
    if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
      RESERVED_SPEC_KEYS[name](Constructor, property);
    } else {
      ...省略特殊处理
      if (shouldAutoBind) {
        ...省略自动绑定相关处理
      } else {
        if (isAlreadyDefined) {
           ...省略已定义容错处理
        } else {
         //关键点  将property赋值给Contructor
          proto[name] = property;
         
        }
      }
    }
  }
}
View Code

通过以上代码就可以大致了解其工作原理了

而ReactClassComponent函数生成代码如下:

var ReactClassComponent = function () {};
_assign(ReactClassComponent.prototype, ReactComponent.prototype, ReactClassMixin);

它的原型是由ReactComponent.prototype及ReactClassMixin复合而成(_assing在根目录 node_modules/fbjs目录下,为facebook工具库中封装的函数,相当于es6 的 Object.assign)

ReactClassMixin源码如下:

var ReactClassMixin = {
  replaceState: function (newState, callback) {
    this.updater.enqueueReplaceState(this, newState);
    if (callback) {
      this.updater.enqueueCallback(this, callback, 'replaceState');
    }
  },
  isMounted: function () {
    return this.updater.isMounted(this);
  }
};
View Code

定义了 replaceState及 isMounted两个方法

至于ReactComponent在./ReactComponent.js文件中,prototype源码如下

ReactComponent.prototype.isReactComponent = {};

//setState方法
ReactComponent.prototype.setState = function (partialState, callback) {
  ...省略报警信息
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

ReactComponent.prototype.forceUpdate = function (callback) {
  this.updater.enqueueForceUpdate(this);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'forceUpdate');
  }
};
View Code

 

2.ReactComponent

ReactComponent的原型请参见上面的代码,其构造函数如下

function ReactComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}
View Code

对于extends 关键字的使用,可以参看babel上对于extends的转换,以了解其运行机制
简单点说,extends转换成ES5有以下两个步骤:

1.Object.create方法去生成对象,即:Cp.prototype=Object.create(ReactComponent.prototpe,{配置对象}) 实现原型继承的目的

2.通过ReactComponent.apply(this,arguments)的方法实现构造函数的继承

实际转换加上属性的验证十分繁杂,有兴趣的同学请亲自实践

这种通过extends方式生成的组件,没有createClass中对getInitialState及getDefaultProps的显示管理

需要研发同学在constructor中进行处理,至于其背后有何机制,以后再做讨论  

三、将jsx object变成 DOMComponent

在react中,组件是用jsx语法书写的,jsx语法在编译成正常js语法时,早期使用的是react官方自身的JSTransform,后来因为其功能与babel jsx编译器功能重复,所以被官方抛弃,现今使用第三方的babel作为jsx编译器。jsx语法编译不在本文范畴之内。

不过通过编码实践以及编译后文件查看我们可以得知,jsx语法的组件被编译器编译成如下格式js语句:

_react2.default.createElement(
                    "div",
                    { className: "bookmenu" },
                    _react2.default.createElement(_Header2.default, { pageTitle: "xxx" }),
                    _react2.default.createElement(_Title2.default, { title: "xxx" })
                );

这其中由于使用ES6 import的缘故,引入模块时会为模块自动添加default作为输出,所以_react2.default其实就是React对象,而在ES5下,则相对清晰:

我们用babel编译器编译jsx文件结果如下:

var HelloBox = React.createClass({
    render: function () {
        return React.createElement(
            "div",
            { className: "someClass" },
            "hello world"
        );
    }
});
ReactDOM.render(React.createElement(HelloBox, null), document.getElementById("app"));

由此可知一个组件的实质是React.createElement方法返回的内容,下面我们将追寻源码中的createElement的调用栈

在react源码中引用的createElement其实是 var createElement = ReactElement.createElement;

找到ReactElement文件:

//示例:React.createElement("div",{ className: "name" },"hello world");
ReactElement.createElement = function (type, config, children) {
  var propName;

  // 属性初始化
  var props = {};
  var key = null;
  var ref = null;
  var self = null;
  var source = null;
  //如果配置对象存在则验证并赋值给属性
  if (config != null) {
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    for (propName in config) {
      if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
        props[propName] = config[propName];
      }
    }
  }
 //获得组件的children,并缓存childArray数组中
  var childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    var childArray = Array(childrenLength);
    for (var i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    if (process.env.NODE_ENV !== 'production') {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }

  // 设置props
  if (type && type.defaultProps) {
    var defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  //省略非生产环境下的配置
  //返回ReactElement函数的返回值
  return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
};
View Code

我们追踪到实际的返回值是ReactElement的执行结果,继续:

ReactElement函数如下:

var ReactElement = function (type, key, ref, self, source, owner, props) {
  var element = {
    // 保存react node type
    $$typeof: REACT_ELEMENT_TYPE,
    // dom type
    type: type,
    key: key,
    ref: ref,
    props: props,
    // 记录负责创建该元素的组件.
    _owner: owner
  };
  //去除非生产环境的配置
  //返回这个element
  return element;
};
View Code

由此我们得知组件的实质是一个结构大致如下的Object

var element = {
  $$typeof: REACT_ELEMENT_TYPE,
  type: type,
  key: key,
  ref: ref,
  props: props,
  _owner: owner,
};

数据类型大致了解,而将组件变成浏览器可预览的dom元素需要使用ReactDOM.render方法

下面就来寻找render方法的实质

在之前提到的build文件夹下的react-dom/lib目录下可以找到ReactDOM.js一窥究竟:

var ReactDOM = {
  findDOMNode: findDOMNode,
  render: ReactMount.render,
  unmountComponentAtNode: ReactMount.unmountComponentAtNode,
  version: ReactVersion,

  /* eslint-disable camelcase */
  unstable_batchedUpdates: ReactUpdates.batchedUpdates,
  unstable_renderSubtreeIntoContainer: renderSubtreeIntoContainer
};

原来render方法是ReactMount.render方法的引用,还是在react-dom目录下,找到ReactMount.js

ReactMount关键源码如下:

var ReactMount = {
  //调用顺序 3
  _renderNewRootComponent: function (nextElement, container, shouldReuseMarkup, context) {
    //此步骤跳向instantiateReactComponent
    var componentInstance = instantiateReactComponent(nextElement, false);
    //批量更新  后面会提到
    ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);
    //为dom节点添加相关ID
    var wrapperID = componentInstance._instance.rootID;
    instancesByReactRootID[wrapperID] = componentInstance;
    //返回已经成为能够被浏览器识别的dom节点
    return componentInstance;
  },

  
  //调用顺序 2
  _renderSubtreeIntoContainer: function (parentComponent, nextElement, container, callback) {
    //省略生产环境的适配及相关处理
    var component = ReactMount._renderNewRootComponent(nextWrappedElement, container, shouldReuseMarkup, nextContext)._renderedComponent.getPublicInstance();
    if (callback) {
      callback.call(component);
    }
    return component;
  },
  //调用顺序 1
  //并没有做什么,直接调用ReactMount._renderSubtreeIntoContainer
  render: function (nextElement, container, callback) {
    return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);
  },

};
View Code

在步骤3的时候又转入到instantiateReactComponent中去处理,这里是将对象转变为DOMComponent的关键所在

function instantiateReactComponent(node, shouldHaveDebugID) {
  var instance;

  if (node === null || node === false) {
    //如果传入的对象为空,则创建空的节点
    instance = ReactEmptyComponent.create(instantiateReactComponent);
  } else if (typeof node === 'object') {
    var element = node;
    //去掉生产环境相关检测
    // 大多数情况下element.type都会是字符串,因此重点查看此内容
    if (typeof element.type === 'string') {
      instance = ReactHostComponent.createInternalComponent(element);
    } else if (isInternalComponentType(element.type)) {
      //如果element.type为函数且prototype不为undefined
      instance = new element.type(element);
      if (!instance.getHostNode) {
        instance.getHostNode = instance.getNativeNode;
      }
    } else {
      //以上两种情况都不是
      instance = new ReactCompositeComponentWrapper(element);
    }
  } else if (typeof node === 'string' || typeof node === 'number') {
    //如果是纯文字则创建文本节点
    instance = ReactHostComponent.createInstanceForText(node);
  } else {
     //忽略兼容性处理  大致是不进行任何操作
  }

  // 用于diff操作的两个属性
  instance._mountIndex = 0;
  instance._mountImage = null;

  return instance;
}
//针对element.type既不是函数也不是字符串,则使用ReactCompositeComponent去生成组件
var ReactCompositeComponentWrapper = function (element) {
  this.construct(element);
};
_assign(ReactCompositeComponentWrapper.prototype, ReactCompositeComponent, {
  _instantiateReactComponent: instantiateReactComponent
});

//ReactCompositeComponent源码如下
var ReactCompositeComponent = {
  //提供construct,以element为参数,为生成的对象附加各种属性
  construct: function (element) {
    this._currentElement = element;
    this._rootNodeID = 0;
    this._compositeType = null;
    this._instance = null;
    this._hostParent = null;
    this._hostContainerInfo = null;

    // See ReactUpdateQueue
    this._updateBatchNumber = null;
    this._pendingElement = null;
    this._pendingStateQueue = null;
    this._pendingReplaceState = false;
    this._pendingForceUpdate = false;

    this._renderedNodeType = null;
    this._renderedComponent = null;
    this._context = null;
    this._mountOrder = 0;
    this._topLevelWrapper = null;

    // See ReactUpdates and ReactUpdateQueue.
    this._pendingCallbacks = null;

    // ComponentWillUnmount shall only be called once
    this._calledComponentWillUnmount = false;

    if (process.env.NODE_ENV !== 'production') {
      this._warnedAboutRefsInRender = false;
    }
  },
View Code

instantiateReactComponent中针对传入的element对象的不同做出不同的处理,关键核心的是调用:

ReactHostComponent.createInternalComponent

ReactHostComponent.createInstanceForText

这两个方法将会把element转化成DOMComponent对象,二者的源码如下:

var genericComponentClass=null;
var textComponentClass=null;
var ReactHostComponentInjection = {
  //接收一个参数作为构造函数
  injectGenericComponentClass: function (componentClass) {
    genericComponentClass = componentClass;
  },
  //接收生成文本节点的构造函数
  injectTextComponentClass: function (componentClass) {
    textComponentClass = componentClass;
  },
  // This accepts a keyed object with classes as values. Each key represents a
  // tag. That particular tag will use this class instead of the generic one.
  injectComponentClasses: function (componentClasses) {
    _assign(tagToComponentClass, componentClasses);
  }
};
//生成dom节点
function createInternalComponent(element) {
  //省略对genericComponentClass在特殊情况下的验证
  //返回由genericComponentClass构造的节点
  return new genericComponentClass(element);
}
//生成文本节点
function createInstanceForText(text) {
  return new textComponentClass(text);
}
//检测是否为文本节点
function isTextComponent(component) {
  return component instanceof textComponentClass;
}
View Code

看到这里整个逻辑似乎是断掉了,两个构造函数都是null,那么它们是如何生成React DOMComponent节点的呢

这还要从ReactDOM.js说起

var ReactDefaultInjection = require('./ReactDefaultInjection');
//执行inject
ReactDefaultInjection.inject();

var ReactDOM = {...};

在ReactDOM文件的开始位置引入了ReactDefaultInjection模块,并执行了它的inject方法

var ReactDOMComponentTree = require('./ReactDOMComponentTree');
var ReactDOMTextComponent = require('./ReactDOMTextComponent');
var ReactInjection = require('./ReactInjection');


function inject() {
  .....
  ReactInjection.HostComponent.injectGenericComponentClass(ReactDOMComponent);

  ReactInjection.HostComponent.injectTextComponentClass(ReactDOMTextComponent);

  .....
}

//ReactInjection模块简版代码如下:

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

var ReactInjection = {
  ....
  HostComponent: ReactHostComponent.injection
  ....
};

//ReactHostComponent.injection如下
var ReactHostComponentInjection = {
  injectGenericComponentClass: function (componentClass) {
    genericComponentClass = componentClass;
  },
  injectTextComponentClass: function (componentClass) {
    textComponentClass = componentClass;
  },
  injectComponentClasses: function (componentClasses) {
    _assign(tagToComponentClass, componentClasses);
  }
};
var ReactHostComponent = {
  createInternalComponent: createInternalComponent,
  createInstanceForText: createInstanceForText,
  isTextComponent: isTextComponent,
  injection: ReactHostComponentInjection
};
View Code

现在我们通过上面的代码分析,node节点构造函数及text节点构造函数是由ReactDOMComponent、ReactDOMTextComponent这两个构造函数构造的

ReactDOMComponent源码如下:

function ReactDOMComponent(element) {
  var tag = element.type;
  validateDangerousTag(tag);
  this._currentElement = element;
  this._tag = tag.toLowerCase();
  this._namespaceURI = null;
  this._renderedChildren = null;
  this._previousStyle = null;
  this._previousStyleCopy = null;
  this._hostNode = null;
  this._hostParent = null;
  this._rootNodeID = 0;
  this._domID = 0;
  this._hostContainerInfo = null;
  this._wrapperState = null;
  this._topLevelWrapper = null;
  this._flags = 0;
  if (process.env.NODE_ENV !== 'production') {
    this._ancestorInfo = null;
    setAndValidateContentChildDev.call(this, null);
  }
}

ReactDOMComponent.displayName = 'ReactDOMComponent';

ReactDOMComponent.Mixin = {
    ....
};

_assign(ReactDOMComponent.prototype, ReactDOMComponent.Mixin, ReactMultiChild);
View Code

ReactDomComponent的构造函数非常简单,同时原型为 ReactDOMComponent.Mixin 和 ReactMultiChild的复合产物

ReactDOMTextComponent的源码如下:

var ReactDOMTextComponent = function (text) {
  // TODO: This is really a ReactText (ReactNode), not a ReactElement
  this._currentElement = text;
  this._stringText = '' + text;
  // ReactDOMComponentTree uses these:
  this._hostNode = null;
  this._hostParent = null;

  // Properties
  this._domID = 0;
  this._mountIndex = 0;
  this._closingComment = null;
  this._commentNodes = null;
};

_assign(ReactDOMTextComponent.prototype, {

  /**
   * Creates the markup for this text node. This node is not intended to have
   * any features besides containing text content.
   *
   * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
   * @return {string} Markup for this text node.
   * @internal
   */
  mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
    if (process.env.NODE_ENV !== 'production') {
      var parentInfo;
      if (hostParent != null) {
        parentInfo = hostParent._ancestorInfo;
      } else if (hostContainerInfo != null) {
        parentInfo = hostContainerInfo._ancestorInfo;
      }
      if (parentInfo) {
        // parentInfo should always be present except for the top-level
        // component when server rendering
        validateDOMNesting(null, this._stringText, this, parentInfo);
      }
    }

    var domID = hostContainerInfo._idCounter++;
    var openingValue = ' react-text: ' + domID + ' ';
    var closingValue = ' /react-text ';
    this._domID = domID;
    this._hostParent = hostParent;
    if (transaction.useCreateElement) {
      var ownerDocument = hostContainerInfo._ownerDocument;
      var openingComment = ownerDocument.createComment(openingValue);
      var closingComment = ownerDocument.createComment(closingValue);
      var lazyTree = DOMLazyTree(ownerDocument.createDocumentFragment());
      DOMLazyTree.queueChild(lazyTree, DOMLazyTree(openingComment));
      if (this._stringText) {
        DOMLazyTree.queueChild(lazyTree, DOMLazyTree(ownerDocument.createTextNode(this._stringText)));
      }
      DOMLazyTree.queueChild(lazyTree, DOMLazyTree(closingComment));
      ReactDOMComponentTree.precacheNode(this, openingComment);
      this._closingComment = closingComment;
      return lazyTree;
    } else {
      var escapedText = escapeTextContentForBrowser(this._stringText);

      if (transaction.renderToStaticMarkup) {
        // Normally we'd wrap this between comment nodes for the reasons stated
        // above, but since this is a situation where React won't take over
        // (static pages), we can simply return the text as it is.
        return escapedText;
      }

      return '<!--' + openingValue + '-->' + escapedText + '<!--' + closingValue + '-->';
    }
  },

  /**
   * Updates this component by updating the text content.
   *
   * @param {ReactText} nextText The next text content
   * @param {ReactReconcileTransaction} transaction
   * @internal
   */
  receiveComponent: function (nextText, transaction) {
    if (nextText !== this._currentElement) {
      this._currentElement = nextText;
      var nextStringText = '' + nextText;
      if (nextStringText !== this._stringText) {
        // TODO: Save this as pending props and use performUpdateIfNecessary
        // and/or updateComponent to do the actual update for consistency with
        // other component types?
        this._stringText = nextStringText;
        var commentNodes = this.getHostNode();
        DOMChildrenOperations.replaceDelimitedText(commentNodes[0], commentNodes[1], nextStringText);
      }
    }
  },

  getHostNode: function () {
    var hostNode = this._commentNodes;
    if (hostNode) {
      return hostNode;
    }
    if (!this._closingComment) {
      var openingComment = ReactDOMComponentTree.getNodeFromInstance(this);
      var node = openingComment.nextSibling;
      while (true) {
        !(node != null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Missing closing comment for text component %s', this._domID) : _prodInvariant('67', this._domID) : void 0;
        if (node.nodeType === 8 && node.nodeValue === ' /react-text ') {
          this._closingComment = node;
          break;
        }
        node = node.nextSibling;
      }
    }
    hostNode = [this._hostNode, this._closingComment];
    this._commentNodes = hostNode;
    return hostNode;
  },

  unmountComponent: function () {
    this._closingComment = null;
    this._commentNodes = null;
    ReactDOMComponentTree.uncacheNode(this);
  }

});
View Code

和ReactDOMComponent一样,同样是简单的构造函数和较为复杂的prototype

至此React将一个jsx语法书写的virtual dom 转变成了能够被js解析的React DOMComponent

 

四、将DOMComponent变成DOM

回到之前的ReactMount.js文件,那里还有最重要的一点,在上面我们知道_renderNewRootComponent是处理virtual dom 对象的最后一环,在这个方法里:

_renderNewRootComponent: function (nextElement, container, shouldReuseMarkup, context) {
    //省略环境校验
    ReactBrowserEventEmitter.ensureScrollValueMonitoring();
    //此步骤为上面提到的将jsx变成DOMComponent
    var componentInstance = instantiateReactComponent(nextElement, false);
   //在拿到DOMComponent后,进行批量更新处理,其中参数中的container就是在ReactDOM.render中传入的 容器dom元素
   ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);
    
    var wrapperID = componentInstance._instance.rootID;
    instancesByReactRootID[wrapperID] = componentInstance;

    return componentInstance;
  },

下面就来看看ReactUpdates.batchedUpdates方法做了什么

//其中参数b是插入组件的dom元素,参数a为DOMComponent
function batchedUpdates(callback, a, b, c, d, e) {
  //组件注入检测
  ensureInjected();
  return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}

关键方法转移到了batchingStrategy.batchedUpdates方法中,和ReactDOMComponent被绑定到ReactHostComponent.createInternalComponent的方式一样,可以查找到batchingStrategy.batchedUpdates其实源于ReactDefaultBatchingStrategy.js中的batchedUpdates方法:

var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,
  batchedUpdates: function (callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
    ReactDefaultBatchingStrategy.isBatchingUpdates = true;
    if (alreadyBatchingUpdates) {
      //如果已经更新过则只执行一次callback
      return callback(a, b, c, d, e);
    } else {
      //否则跳转到transaction.perform  其中 a为DOMComponent  b为被注入的DOM
      return transaction.perform(callback, null, a, b, c, d, e);
    }
  }
};
View Code

由此我们可以追查到transaction.perform方法中去继续查看:

function ReactDefaultBatchingStrategyTransaction() {
  this.reinitializeTransaction();
}
//原型复合了Transaction模块
_assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
  getTransactionWrappers: function () {
    return TRANSACTION_WRAPPERS;
  }
});

var transaction = new ReactDefaultBatchingStrategyTransaction();
View Code

这段代码里用到了callback,该回调函数是在ReactMount.js中传入的:

function batchedMountComponentIntoNode(componentInstance, container, shouldReuseMarkup, context) {
  var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(!shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement);
  transaction.perform(mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context);
  ReactUpdates.ReactReconcileTransaction.release(transaction);
}

//追溯到ReactUpdates.ReactReconcileTransaction
_assign(ReactReconcileTransaction.prototype, Transaction, Mixin);
//并为其附加上面用到的getPooled方法
PooledClass.addPoolingTo(ReactReconcileTransaction);
//addPoolingTo方法如下
var addPoolingTo = function (CopyConstructor, pooler) {
  var NewKlass = CopyConstructor;
  NewKlass.instancePool = [];
  //默认的getPooled方法其实就是DEFAULT_POOLER
  NewKlass.getPooled = pooler || DEFAULT_POOLER;
  //还附加了poolSize属性 默认是10
  if (!NewKlass.poolSize) {
    NewKlass.poolSize = DEFAULT_POOL_SIZE;
  }
  NewKlass.release = standardReleaser;
  return NewKlass;
};
var DEFAULT_POOL_SIZE = 10;
var DEFAULT_POOLER = oneArgumentPooler;

var oneArgumentPooler = function (copyFieldsFrom) {
  //this 其实就是ReactReconcileTransaction
  var Klass = this;
  //管理instancePool,并通过执行this以生成ReactReconcileTransaction的实例
  if (Klass.instancePool.length) {
    var instance = Klass.instancePool.pop();
    Klass.call(instance, copyFieldsFrom);
    return instance;
  } else {
    return new Klass(copyFieldsFrom);
  }
};
View Code

callback也同样执行了Transaction的platform方法,只是参数不同

由此可知重头戏是当前目录下的Transaction.js模块,阅读源码前先看此流程图:

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

通过这个示意图可以推测出Transaction方法其实就是黑箱,通过perform将需要执行的方法导入,然后通过wrapper(也就是this)执行初始化方法,然后执行导入的方法,最后统一执行close方法,而wrapper最终保持不变

perform方法如下所示:

perform: function (method, scope, a, b, c, d, e, f) {
    var errorThrown;
    var ret;
    try {
      this._isInTransaction = true;
      errorThrown = true;
      //执行initalizeAll
      this.initializeAll(0);
      //执行函数
      ret = method.call(scope, a, b, c, d, e, f);
      errorThrown = false;
    } finally {
      try {
        //如果执行出错则close
        if (errorThrown) {
          try {
            this.closeAll(0);
          } catch (err) {}
        } else {
          //总之最后都会执行close
          this.closeAll(0);
        }
      } finally {
        this._isInTransaction = false;
      }
    }
    return ret;
  }
View Code

Transaction会在后面详细介绍

通过Transaction的运作实质上是执行了callback函数,其实就是执行batchedMountComponentIntoNode函数,而其中主要又执行了

mountComponentIntoNode函数,源码如下:

function mountComponentIntoNode(wrapperInstance, container, transaction, shouldReuseMarkup, context) {
  var markerName;
 //省略兼容处理
  var markup = ReactReconciler.mountComponent(wrapperInstance, transaction, null, ReactDOMContainerInfo(wrapperInstance, container), context, 0 /* parentDebugID */
  );
//省略兼容处理
  wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance;
//调用_mountImageIntoNode实现元素的插入
  ReactMount._mountImageIntoNode(markup, container, wrapperInstance, shouldReuseMarkup, transaction);
}
View Code

其中返回的markup对象,经过在ReactDOMComponent中的Mixin.mountComponent方法,将DOMComponent转换为包含dom属性的对象。

Mixin.mountComponent 在生成DOMComponent时作为其构造函数的原型得来的方法

mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
    //this 为DOMComponent对象
    //设置其属性值
    this._rootNodeID = globalIdCounter++;
    this._domID = hostContainerInfo._idCounter++;
    this._hostParent = hostParent;
    this._hostContainerInfo = hostContainerInfo;
    //提取props
    var props = this._currentElement.props;
    //根据标签种类设置其_wrapperState
    switch (this._tag) {
      case 'audio':
      case 'form':
      case 'iframe':
      case 'img':
      case 'link':
      case 'object':
      case 'source':
      case 'video':
        this._wrapperState = {
          listeners: null
        };
    //省略transaction操作    
     ......
    assertValidProps(this, props);
   //根据不同情况设置namespaceURI
    var namespaceURI;
    var parentTag;
    if (hostParent != null) {
      namespaceURI = hostParent._namespaceURI;
      parentTag = hostParent._tag;
    } else if (hostContainerInfo._tag) {
      namespaceURI = hostContainerInfo._namespaceURI;
      parentTag = hostContainerInfo._tag;
    }
    if (namespaceURI == null || namespaceURI === DOMNamespaces.svg && parentTag === 'foreignobject') {
      namespaceURI = DOMNamespaces.html;
    }
    if (namespaceURI === DOMNamespaces.html) {
      if (this._tag === 'svg') {
        namespaceURI = DOMNamespaces.svg;
      } else if (this._tag === 'math') {
        namespaceURI = DOMNamespaces.mathml;
      }
    }
    this._namespaceURI = namespaceURI;
    //省略关于生产环境的处理
    ....
    var mountImage;
    //根据 useCreateElement这个标识的取值决定生成什么样的markup对象
    if (transaction.useCreateElement) {
      var ownerDocument = hostContainerInfo._ownerDocument;
      var el;
      if (namespaceURI === DOMNamespaces.html) {
        if (this._tag === 'script') {
          var div = ownerDocument.createElement('div');
          var type = this._currentElement.type;
          div.innerHTML = '<' + type + '></' + type + '>';
          el = div.removeChild(div.firstChild);
        } else if (props.is) {
          el = ownerDocument.createElement(this._currentElement.type, props.is);
        } else {
          ownerDocument.createElement(this._currentElement.type);
        }
      } else {
        el = ownerDocument.createElementNS(namespaceURI, this._currentElement.type);
      }
      ReactDOMComponentTree.precacheNode(this, el);
      this._flags |= Flags.hasCachedChildNodes;
      if (!this._hostParent) {
        DOMPropertyOperations.setAttributeForRoot(el);
      }
      this._updateDOMProperties(null, props, transaction);
      var lazyTree = DOMLazyTree(el);
      this._createInitialChildren(transaction, props, context, lazyTree);
      mountImage = lazyTree;
    } else {
      var tagOpen = this._createOpenTagMarkupAndPutListeners(transaction, props);
      var tagContent = this._createContentMarkup(transaction, props, context);
      if (!tagContent && omittedCloseTags[this._tag]) {
        mountImage = tagOpen + '/>';
      } else {
        mountImage = tagOpen + '>' + tagContent + '</' + this._currentElement.type + '>';
      }
    }

    switch (this._tag) {
      case 'input':
        transaction.getReactMountReady().enqueue(inputPostMount, this);
        if (props.autoFocus) {
          transaction.getReactMountReady().enqueue(AutoFocusUtils.focusDOMComponent, this);
        }
        break;
      case 'textarea':
        transaction.getReactMountReady().enqueue(textareaPostMount, this);
        if (props.autoFocus) {
          transaction.getReactMountReady().enqueue(AutoFocusUtils.focusDOMComponent, this);
        }
        break;
      case 'select':
        if (props.autoFocus) {
          transaction.getReactMountReady().enqueue(AutoFocusUtils.focusDOMComponent, this);
        }
        break;
      case 'button':
        if (props.autoFocus) {
          transaction.getReactMountReady().enqueue(AutoFocusUtils.focusDOMComponent, this);
        }
        break;
      case 'option':
        transaction.getReactMountReady().enqueue(optionPostMount, this);
        break;
    }
    //mountImage就是最后得到markup对象
    return mountImage;
  }
View Code

然后回到ReactMount中的mountComponentIntoNode函数,最后通过_mountImageIntoNode函数将markup插入到目标DOM元素中去

_mountImageIntoNode: function (markup, container, instance, shouldReuseMarkup, transaction) {
  
    if (shouldReuseMarkup) {
      var rootElement = getReactRootElementInContainer(container);
      if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) {
        ReactDOMComponentTree.precacheNode(instance, rootElement);
        return;
      } else {
        var checksum = rootElement.getAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME);
        rootElement.removeAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME);

        var rootMarkup = rootElement.outerHTML;
        rootElement.setAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME, checksum);

        var normalizedMarkup = markup;
        

        var diffIndex = firstDifferenceIndex(normalizedMarkup, rootMarkup);
        var difference = ' (client) ' + normalizedMarkup.substring(diffIndex - 20, diffIndex + 20) + '\n (server) ' + rootMarkup.substring(diffIndex - 20, diffIndex + 20);

       

       


    if (transaction.useCreateElement) {
      while (container.lastChild) {
        container.removeChild(container.lastChild);
      }
      DOMLazyTree.insertTreeBefore(container, markup, null);
    } else {
      setInnerHTML(container, markup);
      ReactDOMComponentTree.precacheNode(instance, container.firstChild);
    }

    
  }
};
View Code

这个函数里面进行diff运算以及插入操作,将markup对象变为真正的dom元素

文章到此结束

 

posted @ 2016-09-19 21:26  白菜帮子  阅读(1915)  评论(1编辑  收藏  举报