zoukankan      html  css  js  c++  java
  • 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) + '
     (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元素

    文章到此结束

  • 相关阅读:
    第二章 Google guava cache源码解析1--构建缓存器
    第十一章 AtomicInteger源码解析
    JVM虚拟机(五):JDK8内存模型—消失的PermGen
    JVM虚拟机(四):JVM 垃圾回收机制概念及其算法
    Java中RunTime类介绍
    JVM虚拟机(三):参数配置
    ZooKeeperEclipse 插件
    zookeeper(五):Zookeeper中的Access Control(ACL)
    zookeeper(六):Zookeeper客户端Curator的API使用详解
    Java并发编程(九):拓展
  • 原文地址:https://www.cnblogs.com/JhoneLee/p/5886759.html
Copyright © 2011-2022 走看看