• 首页
  • 关于
    • 前端行知录 photo

      前端行知录

      前端路漫漫,行知方知行

    • Email
    • Github
  • 文章
    • 所有文章
    • 所有标签
  • 作品

React 源码概览二 (渲染模块)

08 Sep 2016

Reading time ~5 minutes

React 源码概览二 (渲染模块)

上一篇文章 React 源码概览一 (核心接口) 介绍了 React 源码的核心接口部分,本文继续介绍 React 源码中另一非常重要的模块:渲染。

React.js 定义了通用的模型,但并没有强制限定底层是用 DOM 还是 React Native 渲染,这也是能支持全平台通用的基石。不得不佩服 FB 人的架构能力,下面就介绍其渲染逻辑。

renderers 渲染逻辑

先介绍两个关键的工具: ReactUpdates 和 ReactReconciler。

ReactUpdates

ReactUpdates 提供了批量更新的公共方法。

var ReactUpdates = {
  ReactReconcileTransaction: null,

  batchedUpdates: batchedUpdates,
  enqueueUpdate: enqueueUpdate,
  flushBatchedUpdates: flushBatchedUpdates,
  injection: ReactUpdatesInjection,
  asap: asap,
};

ReactReconciler

ReactReconciler 提供了控制渲染流程的方法。

var ReactReconciler = {
  // 组件初始化,渲染标记,注册事件监听器
  mountComponent: function(
    internalInstance,
    transaction,
    hostParent,
    hostContainerInfo,
    context,
    parentDebugID // 0 in production and for roots
  ) {
    var markup = internalInstance.mountComponent(
      transaction,
      hostParent,
      hostContainerInfo,
      context,
      parentDebugID
    );
    if (internalInstance._currentElement &&
        internalInstance._currentElement.ref != null) {
      transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
    }
    return markup;
  },

  // 得到可以传递给 ReactComponentEnvironment.replaceNodeWithMarkup 的值
  getHostNode: function(internalInstance) {
    return internalInstance.getHostNode();
  },

  // 释放 `mountComponent` 分配的资源
  unmountComponent: function(internalInstance, safely) {
    ReactRef.detachRefs(internalInstance, internalInstance._currentElement);
    internalInstance.unmountComponent(safely);
  },

  // 用新的 element 更新组件
  receiveComponent: function(
    internalInstance, nextElement, transaction, context
  ) {
    var prevElement = internalInstance._currentElement;

    if (nextElement === prevElement &&
        context === internalInstance._context
      ) {
      return;
    }

    var refsChanged = ReactRef.shouldUpdateRefs(
      prevElement,
      nextElement
    );

    if (refsChanged) {
      ReactRef.detachRefs(internalInstance, prevElement);
    }

    internalInstance.receiveComponent(nextElement, transaction, context);

    if (refsChanged &&
        internalInstance._currentElement &&
        internalInstance._currentElement.ref != null) {
      transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
    }
  },

  // 刷新组件的变化
  performUpdateIfNecessary: function(
    internalInstance,
    transaction,
    updateBatchNumber
  ) {
    internalInstance.performUpdateIfNecessary(transaction);
  },
};

Transaction

仅从 ReactUpdates 和 ReactReconciler 就能看出,React 渲染过程中很多的设计和处理都是包含在事务中,关于事务的说明请参考我之前整理的一篇文章 React Transaction 机制。

ReactUpdateQueue

事务处理过程中,对更新的处理都是批量进行,所以必须有队列保存这些更新,而且也需要将事务执行过程中传入的事件缓存下来,这就是 ReactUpdateQueue。还记得 ReactComponent 的 setState 和 forceUpdate 方法吗?实际都是调用 this.updater.enqueueXXX,这里的 updater 实际上对应的就是 ReactUpdateQueue,是在 ReactCompositeComponent 中初始化的。

function enqueueUpdate(internalInstance) {
  ReactUpdates.enqueueUpdate(internalInstance);
}

var ReactUpdateQueue = {
  // 判断是否 mounted
  isMounted: function(publicInstance) {
    var internalInstance = ReactInstanceMap.get(publicInstance);
    if (internalInstance) {
      // During componentWillMount and render this will still be null but after
      // that will always render to something. At least for now. So we can use
      // this hack.
      return !!internalInstance._renderedComponent;
    } else {
      return false;
    }
  },

  // 回调入队,在所有挂起的更新处理完成后执行
  enqueueCallback: function(publicInstance, callback, callerName) {
    ReactUpdateQueue.validateCallback(callback, callerName);
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
    if (!internalInstance) {
      return null;
    }

    if (internalInstance._pendingCallbacks) {
      internalInstance._pendingCallbacks.push(callback);
    } else {
      internalInstance._pendingCallbacks = [callback];
    }
    enqueueUpdate(internalInstance);
  },

  enqueueCallbackInternal: function(internalInstance, callback) {
    if (internalInstance._pendingCallbacks) {
      internalInstance._pendingCallbacks.push(callback);
    } else {
      internalInstance._pendingCallbacks = [callback];
    }
    enqueueUpdate(internalInstance);
  },

  // 强制更新。当不在 DOM 事务时调用。
  enqueueForceUpdate: function(publicInstance) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'forceUpdate'
    );

    if (!internalInstance) {
      return;
    }

    internalInstance._pendingForceUpdate = true;
    enqueueUpdate(internalInstance);
  },

  // 替换所有 state。
  enqueueReplaceState: function(publicInstance, completeState) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'replaceState'
    );

    if (!internalInstance) {
      return;
    }

    internalInstance._pendingStateQueue = [completeState];
    internalInstance._pendingReplaceState = true;

    enqueueUpdate(internalInstance);
  },

  // Sets a subset of the state.
  enqueueSetState: function(publicInstance, partialState) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'setState'
    );

    if (!internalInstance) {
      return;
    }

    var queue =
      internalInstance._pendingStateQueue ||
      (internalInstance._pendingStateQueue = []);
    queue.push(partialState);

    enqueueUpdate(internalInstance);
  },

  enqueueElementInternal: function(internalInstance, nextElement, nextContext) {
    internalInstance._pendingElement = nextElement;
    // TODO: introduce _pendingContext instead of setting it directly.
    internalInstance._context = nextContext;
    enqueueUpdate(internalInstance);
  },

  validateCallback: function(callback, callerName) {},
};

从 enqueueUpdate(internalInstance) 函数可以看出,入队的结果是交由 ReactUpdates.enqueueUpdate(internalInstance) 进行下一步处理。

ReactCompositeComponent

组件间存在层层的嵌套关系,那么必然涉及到复合组件的渲染,下面介绍的 ReactCompositeComponent 就是处理这种场景。

ReactCompositeComponent 包含的逻辑太多,有兴趣的建议去读读源码,其中特别是对生命周期方法的处理比较有意思,能清晰的知道其执行流程或触发条件,有利于代码结构优化,提高性能。关于复合组件间生命周期的调用顺序,参考源码中的说明,已然非常详细。

/**
 * ------------------ The Life-Cycle of a Composite Component ------------------
 *
 * - constructor: Initialization of state. The instance is now retained.
 *   - componentWillMount
 *   - render
 *   - [children's constructors]
 *     - [children's componentWillMount and render]
 *     - [children's componentDidMount]
 *     - componentDidMount
 *
 *       Update Phases:
 *       - componentWillReceiveProps (only called if parent updated)
 *       - shouldComponentUpdate
 *         - componentWillUpdate
 *           - render
 *           - [children's constructors or receive props phases]
 *         - componentDidUpdate
 *
 *     - componentWillUnmount
 *     - [children's componentWillUnmount]
 *   - [children destroyed]
 * - (destroyed): The instance is now blank, released by React and ready for GC.
 *
 * -----------------------------------------------------------------------------
 */

DOM 渲染

本节介绍 DOM 元素的渲染实现。关于 React Native 的渲染,实现方法类似,不再介绍。

var ReactDOM = {
  findDOMNode: findDOMNode,
  render: ReactMount.render,
  unmountComponentAtNode: ReactMount.unmountComponentAtNode,
  version: ReactVersion,

  unstable_batchedUpdates: ReactUpdates.batchedUpdates,
  unstable_renderSubtreeIntoContainer: renderSubtreeIntoContainer,
};

其中最关键的是 render 方法,来自 ReactMount 这个对象。

var ReactMount = {

  TopLevelWrapper: TopLevelWrapper,

  // Take a component that's already mounted into the DOM and replace its props
  _updateRootComponent: function(
      prevComponent,
      nextElement,
      nextContext,
      container,
      callback) {...},

  // Render a new component into the DOM. Hooked by hooks!
  _renderNewRootComponent: function(
    nextElement,
    container,
    shouldReuseMarkup,
    context
  ) {
    ReactBrowserEventEmitter.ensureScrollValueMonitoring();
    var componentInstance = instantiateReactComponent(nextElement, false);

    ReactUpdates.batchedUpdates(
      batchedMountComponentIntoNode,
      componentInstance,
      container,
      shouldReuseMarkup,
      context
    );

    return componentInstance;
  },

  // Renders a React component into the DOM in the supplied `container`.
  renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) {
    return ReactMount._renderSubtreeIntoContainer(
      parentComponent,
      nextElement,
      container,
      callback
    );
  },

  _renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) {...},

  // Renders a React component into the DOM in the supplied `container`.
  render: function(nextElement, container, callback) {
    return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);
  },

  // Unmounts and destroys the React component rendered in the `container`.
  unmountComponentAtNode: function(container) {...},

  _mountImageIntoNode: function(
    markup,
    container,
    instance,
    shouldReuseMarkup,
    transaction
  ) {...},
};

ReactMount 代码太多,不过从关键部分也能看出其处理逻辑。

简单描述下新组件的渲染过程,_renderNewRootComponent 在实例化时调用了 instantiateReactComponent 方法,实际上会调用 ReactCompositeComponent 处理复合组件,并返回一个实例。之后调用了 ReactUpdates.batchedUpdates 将渲染过程委托给 ReactUpdates 进行处理。在 ReactUpdates 中处理时会调用方法 batchedMountComponentIntoNode。

function batchedMountComponentIntoNode(
  componentInstance,
  container,
  shouldReuseMarkup,
  context
) {
  var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
    /* useCreateElement */
    !shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement
  );
  transaction.perform(
    mountComponentIntoNode,
    null,
    componentInstance,
    container,
    transaction,
    shouldReuseMarkup,
    context
  );
  ReactUpdates.ReactReconcileTransaction.release(transaction);
}

batchedMountComponentIntoNode 会传入 transaction,调用其 perform 方法去执行 mountComponentIntoNode 函数。

// Mounts this component and inserts it into the DOM.
function mountComponentIntoNode(
  wrapperInstance,
  container,
  transaction,
  shouldReuseMarkup,
  context
) {
  var markerName;
  if (ReactFeatureFlags.logTopLevelRenders) {
    var wrappedElement = wrapperInstance._currentElement.props.child;
    var type = wrappedElement.type;
    markerName = 'React mount: ' + (
      typeof type === 'string' ? type :
      type.displayName || type.name
    );
    console.time(markerName);
  }

  var markup = ReactReconciler.mountComponent(
    wrapperInstance,
    transaction,
    null,
    ReactDOMContainerInfo(wrapperInstance, container),
    context,
    0 /* parentDebugID */
  );

  if (markerName) {
    console.timeEnd(markerName);
  }

  wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance;
  ReactMount._mountImageIntoNode(
    markup,
    container,
    wrapperInstance,
    shouldReuseMarkup,
    transaction
  );
}

最终,mountComponentIntoNode 中执行 ReactMount._mountImageIntoNode 方法,将生成的 Virtual DOM 真正挂载到 DOM 节点上,整个渲染过程完成。

Virtual DOM

Virtual DOM 是 ReactElement 树,并通过 ReactDOM 的 render 方法渲染到真实 DOM 上。

JSX 是语法糖,但并不是 HTML 的语法糖,其转化后的形式与 ReactElement 保持一致,如下:

var Element = {
  type: 'div',
  props: {
    className: css,
    childlren: []
  }
};

而这正对应着内存中所描述的 Virtual DOM。Virtual DOM 类似于一种缓存机制,大大提升了与 DOM 节点交互的效率。

React

alcat2008

Dreamer, Practitioner, Incomplete Front-ender

← 浏览器工作原理:现代 web 浏览器的幕后机制 React 源码概览一 (核心接口) →