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

你可能感兴趣的:(react)