zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    Vue 移动端向上滑动加载
    关于NPOI 判断Office 是否为空的操作
    定时任务的处理
    Web中线程与IIS线程池自动回收机制
    本地VS调试服务器 IIS 程序
    每天学点node系列-stream
    聊聊前端模块化开发
    位运算解决多标签问题【原创】
    <未来世界的幸存者> 读后感(现实篇和职业篇)【原创】
    Nest.js你学不会系列-初识Nest
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/4502158.html
Copyright © 2011-2022 走看看