React v16-alpha 從virtual dom 到 dom 源碼簡讀

1、物料準備html

1.克隆react源碼, github 地址:https://github.com/facebook/react.gitnode

2.安裝gulp  react

3.在react源碼根目錄下:git

   $npm installes6

   $gulp defaultgithub

   (建議使用node 6.0+)npm

  gulp將文件處理在根目錄下的build文件夾中,打開build查看react的源碼,結構清晰,引用路徑明瞭gulp

2、從生成 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中進行處理,至於其背後有何機制,之後再作討論  

3、將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

 

4、將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元素

文章到此結束

相關文章
相關標籤/搜索