ReactJS-render


ReactJS分析之入口函數render

ReactJS分析之入口函數render

 

前言

            在使用React進行構建應用時,我們總會有一個步驟將組建或者虛擬DOM元素渲染到真實的DOM上,將任務交給瀏覽器,進而進行layout和paint等步驟,這個函數就是React.render()。首先看下該函數的接口定義:

ReactComponent render( ReactElement element, DOMElement container, [function callback] )

接收2-3個參數,並返回ReactComponent類型的對象,當組件被添加到DOM中后,執行回調。在這里涉及到了兩個React類型--ReactComponent和ReactElement,着重分析。

ReactElement類型解讀

         ReactElement類型通過函數React.createElement()創建,接口定義如下:

ReactElement createElement( string/ReactClass type, [object props], [children ...] )

第一個參數可以接受字符串(如“p”,“div”等HTML的tag)或ReactClass,第二個參數為傳遞的參數,第三個為子元素,可以為字符串和ReactElement。

         下面着重分析createElement的具體實現:

復制代碼
ReactElement.createElement = function(type, config, children) {
  var propName;

  // Reserved names are extracted
  var props = {};

  var key = null;
  var ref = null;

  if (config != null) {
    ref = config.ref === undefined ? null : config.ref;
    key = config.key === undefined ? null : '' + config.key;
    // Remaining properties are added to a new props object
    for (propName in config) {
      if (config.hasOwnProperty(propName) &&
          !RESERVED_PROPS.hasOwnProperty(propName)) {
        props[propName] = config[propName];
      }
    }
  }

  // Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
  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];
    }
    props.children = childArray;
  }

  // Resolve default props
  if (type && type.defaultProps) {
    var defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (typeof props[propName] === 'undefined') {
        props[propName] = defaultProps[propName];
      }
    }
  }

  return new ReactElement(
    type,
    key,
    ref,
    ReactCurrentOwner.current,
    ReactContext.current,
    props
  );
};

var ReactElement = function(type, key, ref, owner, context, props) {
  // Built-in properties that belong on the element
  this.type = type;
  this.key = key;
  this.ref = ref;

  // Record the component responsible for creating this element.
  this._owner = owner;

  // TODO: Deprecate withContext, and then the context becomes accessible
  // through the owner.
  this._context = context;

  if ("production" !== process.env.NODE_ENV) {
    // The validation flag and props are currently mutative. We put them on
    // an external backing store so that we can freeze the whole object.
    // This can be replaced with a WeakMap once they are implemented in
    // commonly used development environments.
    this._store = {props: props, originalProps: assign({}, props)};

    // To make comparing ReactElements easier for testing purposes, we make
    // the validation flag non-enumerable (where possible, which should
    // include every environment we run tests in), so the test framework
    // ignores it.
    try {
      Object.defineProperty(this._store, 'validated', {
        configurable: false,
        enumerable: false,
        writable: true
      });
    } catch (x) {
    }
    this._store.validated = false;

    // We're not allowed to set props directly on the object so we early
    // return and rely on the prototype membrane to forward to the backing
    // store.
    if (useMutationMembrane) {
      Object.freeze(this);
      return;
    }
  }

  this.props = props;
};
復制代碼

在ReactElement.js中實現了該方法,首先保存傳入的參數,其中ref和key這兩個參數比較特別,ref用於父組件引用子組件的真實DOM,key用於調和算法,判斷該組件是否update或remove;保存children到props中,並根據type是否有defaultProps屬性對props進行mixin;最后創建ReactElement實例。其中reactElement有個實例屬性_owner,用於保存所屬的組件。

ReactElement的原型對象只有一個簡單的方法用於判斷是否是ReactElement對象,沒有額外的方法。

綜上,我們可以看出ReactElement有4個屬性:type,ref,key,props,並且輕量,沒有狀態,是一個虛擬化的DOM元素。

ReactClass類型解讀

          React的核心是ReactElement類型,但是精髓確實ReactComponent,即組件。但是組件的創建卻並不簡單,我們通過React.createClass創建ReactClass類,它是ReactComponent的構造函數,不同於正常的對象創建,組件的創建由React接管,即我們無須對其實例化(new MyComponent())。相對於ReactElement的無狀態,ReactComponent是有狀態的,先看接口定義:

ReactClass createClass(object specification)

傳入的spec參數必須包含render方法,用於渲染虛擬DOM,render返回ReactElement類型;另外還有一些getInitialState和生命周期方法,可以根據需要定義。

           下面根據createClass的實現來深入分析:

復制代碼
createClass: function(spec) {
    var Constructor = function(props, context) {

      // Wire up auto-binding
      if (this.__reactAutoBindMap) {
        bindAutoBindMethods(this);
      }

      this.props = props;
      this.context = context;
      this.state = null;

      // ReactClasses doesn't have constructors. Instead, they use the
      // getInitialState and componentWillMount methods for initialization.

      var initialState = this.getInitialState ? this.getInitialState() : null;

      this.state = initialState;
    };
    Constructor.prototype = new ReactClassComponent();
    Constructor.prototype.constructor = Constructor;

    injectedMixins.forEach(
      mixSpecIntoComponent.bind(null, Constructor)
    );

    mixSpecIntoComponent(Constructor, spec);

    // Initialize the defaultProps property after all mixins have been merged
    if (Constructor.getDefaultProps) {
      Constructor.defaultProps = Constructor.getDefaultProps();
    }

    // Reduce time spent doing lookups by setting these on the prototype.
    for (var methodName in ReactClassInterface) {
      if (!Constructor.prototype[methodName]) {
        Constructor.prototype[methodName] = null;
      }
    }

    // Legacy hook
    Constructor.type = Constructor;

    return Constructor;
  }
// Constructor的原型
var ReactClassComponent = function() {};
// assign類似於mixin
assign(
  ReactClassComponent.prototype,
  ReactComponent.prototype,
  ReactClassMixin
);

// mixin到Constructor的原型上
function mixSpecIntoComponent(Constructor, spec) {
  if (!spec) {
    return;
  }

  var proto = Constructor.prototype;

  // By handling mixins before any other properties, we ensure the same
  // chaining order is applied to methods with DEFINE_MANY policy, whether
  // mixins are listed before or after these methods in the spec.
  if (spec.hasOwnProperty(MIXINS_KEY)) {
    RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins);
  }

  for (var name in spec) {
    if (!spec.hasOwnProperty(name)) {
      continue;
    }

    if (name === MIXINS_KEY) {
      // We have already handled mixins in a special case above
      continue;
    }

    var property = spec[name];
    validateMethodOverride(proto, name);

    if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
      RESERVED_SPEC_KEYS[name](Constructor, property);
    } else {
      // Setup methods on prototype:
      // The following member methods should not be automatically bound:
      // 1. Expected ReactClass methods (in the "interface").
      // 2. Overridden methods (that were mixed in).
      var isReactClassMethod =
        ReactClassInterface.hasOwnProperty(name);
      var isAlreadyDefined = proto.hasOwnProperty(name);
      var markedDontBind = property && property.__reactDontBind;
      var isFunction = typeof property === 'function';
      var shouldAutoBind =
        isFunction &&
        !isReactClassMethod &&
        !isAlreadyDefined &&
        !markedDontBind;

      if (shouldAutoBind) {
        if (!proto.__reactAutoBindMap) {
          proto.__reactAutoBindMap = {};
        }
        proto.__reactAutoBindMap[name] = property;
        proto[name] = property;
      } else {
        if (isAlreadyDefined) {
          var specPolicy = ReactClassInterface[name];

          // For methods which are defined more than once, call the existing
          // methods before calling the new property, merging if appropriate.
          if (specPolicy === SpecPolicy.DEFINE_MANY_MERGED) {
            proto[name] = createMergedResultFunction(proto[name], property);
          } else if (specPolicy === SpecPolicy.DEFINE_MANY) {
            proto[name] = createChainedFunction(proto[name], property);
          }
        } else {
          proto[name] = property;
          if ("production" !== process.env.NODE_ENV) {
            // Add verbose displayName to the function, which helps when looking
            // at profiling tools.
            if (typeof property === 'function' && spec.displayName) {
              proto[name].displayName = spec.displayName + '_' + name;
            }
          }
        }
      }
    }
  }
}
復制代碼

        createClass返回一個Constructor構造函數,它的原型是new ReactClassComponent()對象,該對象有mixin的組件的方法(在spec對象中的mixins屬性的對象的方法)和ReactComponent的方法(setState和forceUpdate),並且在mixSpecIntoComponent(Constructor, spec)方法中將spec中實現的方法綁定到Constructor的原型上,在這里對於非React提供的方法(即個人實現的一些功能函數或者事件處理函數)保存在原型的__reactAutoBindMap的屬性上。最后再設置Constructor的defaultProps和type(Constructor.type = Constructor)。

        在上節中提到了createElement的第一個參數可以是ReactClass,因此在Constructor實現上賦予了type和defaultProps屬性。

React的入口—React.render()

           React.render的實現是在ReactMount中,我們通過源碼進行進一步的分析。

復制代碼
render: function(nextElement, container, callback) {

    var prevComponent = instancesByReactRootID[getReactRootID(container)];

    if (prevComponent) {
      var prevElement = prevComponent._currentElement;
      if (shouldUpdateReactComponent(prevElement, nextElement)) {
        return ReactMount._updateRootComponent(
          prevComponent,
          nextElement,
          container,
          callback
        ).getPublicInstance();
      } else {
        ReactMount.unmountComponentAtNode(container);
      }
    }

    var reactRootElement = getReactRootElementInContainer(container);
    var containerHasReactMarkup =
      reactRootElement && ReactMount.isRenderedByReact(reactRootElement);

    var shouldReuseMarkup = containerHasReactMarkup && !prevComponent;

    var component = ReactMount._renderNewRootComponent(
      nextElement,
      container,
      shouldReuseMarkup
    ).getPublicInstance();
    if (callback) {
      callback.call(component);
    }
    return component;
  }
復制代碼

         如果是第一次掛載該ReactElement,直接添加即可;如果之前已掛載過,則通過instancesByReactRootID獲取渲染之前container的舊組件,即prevComponent,具體通過獲取container的firstChild,並根據緩存獲取該對象對應的id,並根據id得到prevComponent。每個component對象都有對應的虛擬DOM,即ReactElement,通過shouldUpdateReactComponent(prevElement, nextElement)進行判斷對組件進行update還是delete。

         具體shouldUpdateReactComponent的比較算法是:如果prevElement類型為string或者number,那么nextElement類型為string或number時為true;如果prevElement和nextElement為object,並且key和type屬性相同,則prevElement._owner == nextElement._owner相等時為true,否則為false。

             如果需要更新,則調用ReactMount.._updateRootComponent函數進行Reconciliation,並返回該組件;否則刪除該組件,具體操作則是刪除container的所有子元素。然后判斷shouldReuseMarkup,對於初次掛載的ReactElement而言,該標記為false。最后通過調用_renderNewRootComponent方法將ReactElement渲染到DOM上,並獲取對應的ReactComponent對象,最后執行回調並返回組件對象。

          對於_renderNewRootComponent方法,通過調用instantiateReactComponent(nextElement, null)來實例化組件,並在ReactMount的緩存中注冊組件,批量執行更新ReactUpdates.batchedUpdates,最終通過_mountImageIntoNode方法將虛擬節點插入到DOM中。

          至此,React中比較重要的方法講解完畢。下一步計划是分析組件的實例化過程,敬請期待。

 
分類:  Pure JS
標簽:  React


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM