ReactDOM.render到render流程概览

444 阅读3分钟

本文较为基础,适合react新手阅读。阅读过程中如果发现任何错误,麻烦评论区指正。
React版本:React17.0.0。

引言

由于项目渲染多个根组件或者通过多次render的方式进行更新等情况不常见,所以本文跳过update阶段相关的逻辑,只探讨React初次渲染阶段且只渲染当个根组件的情况。

ReactDOM.render

/**
* @param element - 要渲染的 React 元素
* @param container - 要进行渲染的 DOM 元素
* @param callback - 在渲染完成后要执行的函数
* @return container - 包含已渲染组件的容器
*/
function render(element, container, callback) {
  // ...
   return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
}

render函数中主要关注legacyRenderSubtreeIntoContainer函数。

legacyRenderSubtreeIntoContainer

function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
  var root = container._reactRootContainer;
  var fiberRoot;

   if (typeof callback === 'function') {
      var originalCallback = callback;

      callback = function () {
        var instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    } //处理回调

  if (!root) {
    // 初次渲染,如果没有root,则去创建root
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
    fiberRoot = root._internalRoot;

    unbatchedUpdates(function () {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    //...
  }

  return getPublicRootInstance(fiberRoot);
}

首先会获取root,根据root的值来判断是否是mount阶段。

下边是container的结构:

{
    _reactRootContainer:{
        _internalRoot:FiberRoot
    }
}

结构中_internalRoot对应的值就是项目的FiberRoot,是项目的Fiber根节点,一个项目只会有一个,是唯一的。 如果项目是初次渲染的话,root不存在,则会进入到创建root、FiberRoot和rootFiber的逻辑中,也就是legacyCreateRootFromDOMContainer方法。

legacyCreateRootFromDOMContainer

在该方法中会一路调用:createLegacyRoot -> new ReactDOMBlockingRoot -> createRootImpl -> createContainer -> createFiberRoot
调用的层级可能会有点深,但是可以将legacyCreateRootFromDOMContainer主要做的事情可以总结为如下代码:

function legacyCreateRootFromDOMContainer(container, forceHydrate) {
    //创建FiberRoot节点
    var root = new FiberRootNode(containerInfo, tag, hydrate);
    //根据项目启动的不同类型,创建不同类型的RootFiber,核心仍然是创建RootFiber(tag为3)。
    var uninitializedFiber = createHostRootFiber(tag);
  
    // 建立FiberRoot与RootFiber之间的联系
    root.current = uninitializedFiber;
    uninitializedFiber.stateNode = root;
    
    // 对于HostRoot或者ClassComponent会使用initializeUpdateQueue创建updateQueue,
    // 然后将updateQueue挂载到fiber节点上
    initializeUpdateQueue(uninitializedFiber);
    return root;
    
}

//updateQueue是React状态更新相关的数据结构
function initializeUpdateQueue(fiber) {
  var queue = {
    baseState: fiber.memoizedState,
    firstBaseUpdate: null,
    lastBaseUpdate: null,
    shared: {
      pending: null
    },
    effects: null
  };
  fiber.updateQueue = queue;
}

上述代码作用的是current Fiber树,current Fiber经过创建后,是存在fiberRoot节点和rootFiber节点的。这也就是说后续render阶段,处理workInProgress Fiber树的RootFiber节点不需要创建,而是通过Fiber双缓存的alternate指针获取Current Fiber树的RootFiber节点进行复用更新处理。

updateContainer

在创建好FiberRoot和rootFiber之后,就会走到updateContainer函数,


function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
  {

    //...
    unbatchedUpdates(function () {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
    //...

}

在创建完FiberRoot和RootFiber之后,会以非批量更新的形式执行updateContainer方法,无需等待 React 的批量更新机制,同步执行。

function updateContainer(element, container, parentComponent, callback) {

  // current$1为Current Fiber树的RootFiber。
  // 参数container为FiberRoot,FiberRoot.current则指向的就是之前创建的RootFiber
  var current$1 = container.current;
  var eventTime = requestEventTime();
  var lane = requestUpdateLane(current$1);

  // 创建一个update对象,payload为根组件的jsx对象。
  var update = createUpdate(eventTime, lane);
  update.payload = {
    element: element
  };
  
  //处理render传入的回调,当该update更新后进行的回调
  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    {
      if (typeof callback !== 'function') {
        error('render(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback);
      }
    }

    update.callback = callback;
  }
  //将update插入到fiber节点的updateQueue中
  enqueueUpdate(current$1, update);
  //调度Current RootFiber节点的更新
  scheduleUpdateOnFiber(current$1, lane, eventTime);
  return lane;
}

enqueueUpdate的作用就是将刚刚创建好的update加入到RootFiber的updateQueue.pending中,等到进入render阶段处理updateQueue创建根组件的Fiber节点。

function enqueueUpdate(fiber, update) {
  var updateQueue = fiber.updateQueue;

  if (updateQueue === null) {
    // Only occurs if the fiber has been unmounted.
    return;
  }

  var sharedQueue = updateQueue.shared;
  var pending = sharedQueue.pending;
  
    // 这是一个环形链表的处理,将新创建的update插入到环形链表的尾部,
    // 并将尾指针(sharedQueue.pending)进行更新。
  if (pending === null) {
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }

  sharedQueue.pending = update;
}
scheduleUpdateOnFiber
function scheduleUpdateOnFiber(fiber, lane, eventTime) {
  var root = markUpdateLaneFromFiberToRoot(fiber, lane);

  if (root === workInProgressRoot) {
      var priorityLevel = getCurrentPriorityLevel();

      if (lane === SyncLane) {//同步 legacy模式
          performSyncWorkOnRoot(root);
      } else {// concurrent模式
        ensureRootIsScheduled(root, eventTime);//将performConcurrentWorkOnRoot函数调度执行
      }
      mostRecentlyUpdatedRoot = root;
}

最终都会执行performSyncWorkOnRoot或者performConcurrentWorkOnRoot进入到render模式。

总结

ReactDOM.render到render阶段之间的流程是:

  1. 创建FiberRoot和RootFiber节点,并将二者建立起联系(FiberRoot.current=RootFiber)
  2. 初始化RootFiber的updaateQueue,并创建payload为根组件的update对象加入到updateQueue中
  3. 开始调度root的更新,根据mode的不同,执行不同mode的启动render阶段的方法