【React】ReactDOM.render源码分析

这是一个hello world,你造咩

ReactDOM.render(
  <h1>Hello, world!h1>,
  document.getElementById('example')
);

ReactDOM.render()实际调用ReactMount.render()

  /**
   * @param {ReactElement} nextElement 要插入到DOM中的组件
   * @param {DOMElement} container 要插入到的容器
   * @param {?function} callback 回调
   * @return {ReactComponent} Component instance rendered in `container`.返回ReactComponent
   */
  render: function (nextElement, container, callback) {
  },

可见render方法的第一个参数是一个ReactElement类型的变量,那么

Hello, world!

是如何转成成为ReactElement类型变量的呢?快往下看呀

ReactElement

JSX中创建React元素最终会被babel转译为createElement(type, config, children), babel根据JSX中标签的首字母来判断是原生DOM组件,还是自定义React组件

"wrap" > <span>123span> <My className='my' content={456} /> div>

上面这段代码转义完后如下:

React.createElement(
        type: 'div', 
        props:{
            className: 'wrap',
        },
        children: [
            React.createElement(
                type: 'span', 
                props: null, 
                chilren: '123'
            ), 
            React.createElement(
                type:My.default//从其他文件中引入的React组件  
                props: {
                    className: 'my',
                    content: 456
                }
            ) 
        ]   
)

所以createElement做了什么呢?

/**
 * Factory method to create a new React element. This no longer adheres to
 * the class pattern, so do not use new to call it. Also, no instanceof check
 * will work. Instead test $$typeof field against Symbol.for('react.element') to check
 * if something is a React Element.
 *
 * @param {*} type
 * @param {*} key
 * @param {string|object} ref
 * @param {*} self A *temporary* helper to detect places where `this` is
 * different from the `owner` when React.createElement is called, so that we
 * can warn. We want to get rid of owner and replace string `ref`s with arrow
 * functions, and as long as `this` and owner are the same, there will be no
 * change in behavior.
 * @param {*} source An annotation object (added by a transpiler or otherwise)
 * indicating filename, line number, and/or other information.
 * @param {*} owner
 * @param {*} props
 * @internal
 */
var ReactElement = function(type, key, ref, self, source, owner, props) {
  var element = {
    // This tag allow us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,
    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,
    // Record the component responsible for creating this element.
    _owner: owner,
  };
  return element;
};

/**
 * 创建指定类型的React元素节点
*/
ReactElement.createElement = function(type, config, children) {
  var propName;

  // Reserved names are extracted
  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;
    // Remaining properties are added to a new props object
    for (propName in config) {
      if (
        hasOwnProperty.call(config, 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.
  // 传过来的children被放在了props
  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;
  }

  // 静态变量defaultProps,属性设置默认值
  if (type && type.defaultProps) {
    var defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }

  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
};

由此可见,createElement()接收三个参数(type,config,children),做了一些变量初始化,接着调用了ReactElement()方法。
ReactElement()是一个工厂方法,根据传入的参数返回一个element对象,也是我们一直所说的ReactElement,如下
【React】ReactDOM.render源码分析_第1张图片

【React】ReactDOM.render源码分析_第2张图片

接下来我们来看一看render()做了什么

这是一串函数

告诉自己,我不晕

/**
 * Mounting is the process of initializing a React component by creating its
 * representative DOM elements and inserting them into a supplied `container`.
 * Any prior content inside `container` is destroyed in the process.
 *
 *   ReactMount.render(
 *     component,
 *     document.getElementById('container')
 *   );
 *
 *   
<-- Supplied `container`. *
<-- Rendered reactRoot of React * // ... component. *
*
* * Inside of `container`, the first element rendered is the "reactRoot". */
var ReactMount = { /**入口render方法 * @param {ReactElement} nextElement 要插入到DOM中的组件 * @param {DOMElement} container 要插入到的容器 * @param {?function} callback 回调 * @return {ReactComponent} Component instance rendered in `container`.返回ReactComponent */ render: function (nextElement, container, callback) { return ReactMount._renderSubtreeIntoContainer( null, nextElement, container, callback, ); }, /** * 将ReactElement插入DOM中,并返回ReactElement对应的ReactComponent。 * ReactElement是React元素在内存中的表示形式,可以理解为一个数据类,包含type,key,refs,props等成员变量 * ReactComponent是React元素的操作类,包含mountComponent(), updateComponent()等很多操作组件的方法 */ _renderSubtreeIntoContainer: function ( parentComponent, nextElement, container, callback, ) { callback = callback === undefined ? null : callback; var nextWrappedElement = React.createElement(TopLevelWrapper, { child: nextElement, }); var nextContext = getContextForSubtree(parentComponent); // 获取要插入到的容器的前一次的ReactComponent,这是为了做DOM diff var prevComponent = getTopLevelWrapperInContainer(container); if (prevComponent) { var prevWrappedElement = prevComponent._currentElement; var prevElement = prevWrappedElement.props.child; // shouldUpdateReactComponent方法判断是否需要更新,它只对同一DOM层级,type相同,key(如果有)相同的组件做DOM diff, if (shouldUpdateReactComponent(prevElement, nextElement)) { var publicInst = prevComponent._renderedComponent.getPublicInstance(); var updatedCallback = callback && function () { validateCallback(callback); callback.call(publicInst); }; ReactMount._updateRootComponent( prevComponent, nextWrappedElement, nextContext, container, updatedCallback, ); return publicInst; } else { //直接unmount ReactMount.unmountComponentAtNode(container); } } // 对于ReactDOM.render()调用,prevComponent为null var reactRootElement = getReactRootElementInContainer(container); var containerHasReactMarkup = reactRootElement && !!internalGetID(reactRootElement); var containerHasNonRootReactChild = hasNonRootReactChild(container); var shouldReuseMarkup = containerHasReactMarkup && !prevComponent && !containerHasNonRootReactChild; var component = ReactMount._renderNewRootComponent( nextWrappedElement, container, shouldReuseMarkup, nextContext, callback, )._renderedComponent.getPublicInstance(); return component; }, /** * Render a new component into the DOM. Hooked by hooks! * * @param {ReactElement} nextElement element to render * @param {DOMElement} container container to render into * @param {boolean} shouldReuseMarkup if we should skip the markup insertion * @return {ReactComponent} nextComponent */ _renderNewRootComponent: function ( nextElement, container, shouldReuseMarkup, context, callback, ) { //初始化ReactComponent,根据ReactElement中不同的type字段,创建不同类型的组件对象,即ReactComponent var componentInstance = instantiateReactComponent(nextElement, false); if (callback) { //。。。。 } // The initial render is synchronous but any updates that happen during // rendering, in componentWillMount or componentDidMount, will be batched // according to the current batching strategy. // 处理batchedMountComponentIntoNode方法调用,将ReactComponent插入DOM中 ReactUpdates.batchedUpdates( batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context, ); var wrapperID = componentInstance._instance.rootID; instancesByReactRootID[wrapperID] = componentInstance; return componentInstance; }, _mountImageIntoNode: function( markup, container, instance, shouldReuseMarkup, transaction, ) { //如变量名,是否复用markup,ReactDOM.render()调用为false 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; } } if (transaction.useCreateElement) { while (container.lastChild) { container.removeChild(container.lastChild); } DOMLazyTree.insertTreeBefore(container, markup, null); } else { // 利用innerHTML将markup插入到container这个DOM元素上 setInnerHTML(container, markup); // 将instance(Virtual DOM)保存到container这个DOM元素的firstChild这个原生节点上 ReactDOMComponentTree.precacheNode(instance, container.firstChild); } }, } /** * Batched mount. 以transaction事务的形式调用mountComponentIntoNode * @param {ReactComponent} componentInstance The instance to mount. * @param {DOMElement} container DOM element to mount into. * @param {boolean} shouldReuseMarkup If true, do not insert markup */ function batchedMountComponentIntoNode( componentInstance, container, shouldReuseMarkup, context, ) { var transaction = ReactUpdates.ReactReconcileTransaction.getPooled( /* useCreateElement */ !shouldReuseMarkup, ); transaction.perform( mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context, ); ReactUpdates.ReactReconcileTransaction.release(transaction); } /** * Mounts this component and inserts it into the DOM. * * @param {ReactComponent} componentInstance The instance to mount. * @param {DOMElement} container DOM element to mount into. * @param {ReactReconcileTransaction} transaction * @param {boolean} shouldReuseMarkup If true, do not insert markup */ function mountComponentIntoNode( wrapperInstance, container, transaction, shouldReuseMarkup, context, ) { //调用对应ReactComponent中的mountComponent方法来渲染组件,返回React组件解析后的HTML var markup = ReactReconciler.mountComponent( wrapperInstance, transaction, null, ReactDOMContainerInfo(wrapperInstance, container), context, 0 /* parentDebugID */, ); wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance; // 将解析出来的HTML插入DOM中 ReactMount._mountImageIntoNode( markup, container, wrapperInstance, shouldReuseMarkup, transaction, ); } /** * Given a ReactNode, create an instance that will actually be mounted. * 根据ReactElement中不同的type字段,创建不同类型的组件对象 * * ReactEmptyComponent.create(), 创建空对象ReactDOMEmptyComponent * ReactNativeComponent.createInternalComponent(), 创建DOM原生对象 ReactDOMComponent * new ReactCompositeComponentWrapper(), 创建React自定义对象ReactCompositeComponent * ReactNativeComponent.createInstanceForText(), 创建文本对象 ReactDOMTextComponent * * @param {ReactNode} node * @param {boolean} shouldHaveDebugID * @return {object} A new instance of the element's constructor. * @protected */ function instantiateReactComponent(node, shouldHaveDebugID) { var instance; if (node === null || node === false) { // 空对象 instance = ReactEmptyComponent.create(instantiateReactComponent); } else if (typeof node === 'object') { // 组件对象,包括DOM原生的和React自定义组件 var element = node; var type = element.type; if (typeof element.type === 'string') { // DOM原生对象 instance = ReactHostComponent.createInternalComponent(element); } else if (isInternalComponentType(element.type)) { // This is temporarily available for custom components that are not string // representations. I.e. ART. Once those are updated to use the string // representation, we can drop this code path. instance = new element.type(element); // We renamed this. Allow the old name for compat. :( if (!instance.getHostNode) { instance.getHostNode = instance.getNativeNode; } } else { // React自定义组件 instance = new ReactCompositeComponentWrapper(element); } } else if (typeof node === 'string' || typeof node === 'number') { // 文本对象 instance = ReactHostComponent.createInstanceForText(node); } else { // 报error } // 还记得dom diff中的_mountIndex吗? instance._mountIndex = 0; instance._mountImage = null; return instance; }

好吧,我也晕了。。
大体过程如下:

【React】ReactDOM.render源码分析_第3张图片

你可能感兴趣的:(react)