C
Home
Tags

About
Links

RSS
React源码阅读 1 —— 组件的创建及渲染
2018-04-29

组件使你可以将 UI 划分为一个一个独立、可复用的小部件,并可以对每个部件进行单独的设计。

1.前言

  1. 默认大家对React的常用api比较熟悉。
  2. 阅读版本为 v15.4.1。
  3. 理解解析若有误,还望指出讨论。

2.组件

当我们使用ES6写React的时候,class A extends React.Component信手拈来,那么React.Component到底是什么,我们来看一下

var React = {
  // Modern
  Component: ReactBaseClasses.Component,
  PureComponent: ReactBaseClasses.PureComponent,
  createElement: createElement,

  // Classic
  PropTypes: ReactPropTypes,
  createClass: createReactClass,
  //...
};

ReactBaseClasses.js

function ReactComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

ReactComponent.prototype.isReactComponent = {};

ReactComponent.prototype.setState = function (partialState, callback) {
  //...
};

ReactComponent.prototype.forceUpdate = function (callback) {
  //...
};

我们可以看到React.Component就是一个基类,有props、refs和setState等我们常用的属性及方法。 除了React.Component,我们发现还有一个React.PureComponent,两者的区别就是在组件更新的时候,如果组件内没有定义shouldComponentUpdate钩子函数时,PureComponent会对state进行浅比较决定是否更新。

updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {
  // ...
  if (!this._pendingForceUpdate) {
      if (inst.shouldComponentUpdate) {
        // ...
      } else {
        if (this._compositeType === CompositeTypes.PureClass) {
          shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState);
        }
      }
    }
}

如果不用ES6 class声明,我们可以用React.createClass创建,只是其中需要用getInitialState等特定api,其他实现几乎一样。

3.React元素

ReactElement.js

var ReactElement = function (type, key, ref, self, source, owner, props) {
  var element = {
    $$typeof: REACT_ELEMENT_TYPE,

    type: type,
    key: key,
    ref: ref,
    props: props,
  };
  // ...
  return element;
}

ReactElement.createElement = function (type, config, children) {
  // ...
  return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
};

数据类,JSX元素语法糖及我们通过class声明的React组件的render方法实际调用的就是createElement,最终返回一个ReactElement对象。

4.元素的渲染

我们都知道ReactDOM.render方法将React元素渲染到参数提供的DOM中,如果先前已经被渲染了,那么将对其更新。接下来我们就来深入了解下整个过程。

创建组件

ReactDOM.renderReactMount.render的引用,进一步调用_renderSubtreeIntoContainer方法。

ReactMount.js

_renderSubtreeIntoContainer: function (parentComponent, nextElement, container, callback) {
  // ...
  // 获取当前容器下已存在组件
  var prevComponent = getTopLevelWrapperInContainer(container);

  if (prevComponent) {
    //是否更新组件
    if (shouldUpdateReactComponent(prevElement, nextElement)) {
      ReactMount._updateRootComponent(prevComponent, nextWrappedElement, nextContext, container, updatedCallback);
      return publicInst;
    } else {
      // 卸载组件
      ReactMount.unmountComponentAtNode(container);
    }
  }
  // ...
  var component = ReactMount._renderNewRootComponent(nextWrappedElement, container, shouldReuseMarkup, nextContext)._renderedComponent.getPublicInstance();
  return component;
}

render: function (nextElement, container, callback) {
  return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);
}

方法首先判断了接下来要进行的是渲染还是更新,简单看一下判断逻辑:

  1. getTopLevelWrapperInContainer获取当前容器下已存在组件prevComponent
  2. prevComponent存在,且shouldUpdateReactComponent(对存在的element和待渲染的element的数据类型及type、key属性值进行比较判断),则调用_updateRootComponent更新组件,否则最终执行_renderNewRootComponent渲染元素。

接着,我们来看下_renderNewRootComponent方法。

_renderNewRootComponent: function (nextElement, container, shouldReuseMarkup, context) {
  // ...
  var componentInstance = instantiateReactComponent(nextElement, false);
  ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);
  return componentInstance;
}

我们可以看到组件就是在这里创建:

  1. instantiateReactComponent创建组件。
  2. ReactUpdates.batchedUpdates判断异步渲染时是否有组件更新,根据当前策略做对应处理(是否已事务的形式执行batchedMountComponentIntoNode)。

重点来看下instantiateReactComponent方法。

instantiateReactComponent.js

function instantiateReactComponent(node, shouldHaveDebugID) {
  if (node === null || node === false) {
    instance = ReactEmptyComponent.create(instantiateReactComponent);
  } else if (typeof node === 'object') {
    if (typeof element.type === 'string') {
      instance = ReactHostComponent.createInternalComponent(element);
    } else if (isInternalComponentType(element.type)) {
      instance = new element.type(element);
    } else {
      instance = new ReactCompositeComponentWrapper(element);
    }
  } else if (typeof node === 'string' || typeof node === 'number') {
    instance = ReactHostComponent.createInstanceForText(node);
  }
  return instance;
}

方法根据传入的node的数据类型返回不同类型的组件,大致有ReactDOMEmptyComponent、ReactCompositeComponent、ReactDOMComponent、ReactDOMTextComponent类型等。其中ReactCompositeComponent是我们比较常用的组件,其具有完整生命周期的钩子,后面我们会单独讲解。

最后batchedMountComponentIntoNode则以事务形式调用mountComponentIntoNode方法进行后续渲染。

解析组件插入DOM

mountComponentIntoNode方法中主要就是解析组件。

function mountComponentIntoNode(wrapperInstance, container, transaction, shouldReuseMarkup, context) {
  // ...
  var markup = ReactReconciler.mountComponent(wrapperInstance, transaction, null, ReactDOMContainerInfo(wrapperInstance, container), context, 0);
  ReactMount._mountImageIntoNode(markup, container, wrapperInstance, shouldReuseMarkup, transaction);
}

我们之前看到instantiateReactComponent会获取到不同的组件类型,而且他们对mountComponent方法有不同的实现。ReactReconciler.mountComponent就是调用第一个参数wrapperInstancemountComponent方法解析出Html结构记为markup,然后调用_mountImageIntoNode插入到DOM中。

_mountImageIntoNode: function (markup, container, instance, shouldReuseMarkup, transaction) {
  // ...
  setInnerHTML(container, markup);
  ReactDOMComponentTree.precacheNode(instance, container.firstChild);
  // ...
}