React render源码详解

291 阅读4分钟

create-react-app创建的项目中,是调用ReactDOM.render

const initApp = () => {
    ReactDOM.render(<div></div>, document.getElementById('root'))
}
initApp()

最后调用的是ReactRoot的render方法。

ReactRoot详解

function ReactRoot(
  container: DOMContainer,
  isConcurrent: boolean,
  hydrate: boolean,
) {
  // 这个 root 指的是 FiberRoot
  const root = createContainer(container, isConcurrent, hydrate);
  this._internalRoot = root;
}
// 作用是检查是否在render方法中传入callback,有传就放入ReactWork中的_callbacks
// 然后调用updateContainer
ReactRoot.prototype.render = function(
  children: ReactNodeList,
  callback: ?() => mixed,
): Work {
  // 这里指 FiberRoot
  const root = this._internalRoot;
  // ReactWork 的功能就是为了在组件渲染或更新后把所有传入
  // ReactDom.render 中的回调函数全部执行一遍
  const work = new ReactWork();
  callback = callback === undefined ? null : callback;
  if (__DEV__) {
    warnOnInvalidCallback(callback, 'render');
  }
  // 如果有 callback,就 push 进 work 中的数组
  if (callback !== null) {
    work.then(callback);
  }
  // work._onCommit 就是用于执行所有回调函数的
  updateContainer(children, root, null, work._onCommit);
  return work;
};

 // 调用updateContainer
ReactRoot.prototype.unmount = function(){
  return updateContainer(null, root, null, work._onCommit);
}
// 
ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function(){
  // parentComponent, children, callback传入
  return updateContainer(children, root, parentComponent, work._onCommit);
}
ReactRoot.prototype.createBatch = function(){
  const batch = new ReactBatch(this);
  // internalRoot.firstBatch存在,就插已有batch的尾巴上。否者将firstBatch设置为这个新batch,并设置batch的next为null
  return batch;
}

createContainer最终由createFiberRoot返回,

export function createFiberRoot(
  containerInfo: any,
  isConcurrent: boolean,
  hydrate: boolean,
): FiberRoot {
  // FiberRootNode 内部创建了很多属性
  const root: FiberRoot = (new FiberRootNode(containerInfo, hydrate): any);
  // 创建一个 root fiber
  // fiber 其实也会组成一个树结构,内部使用了单链表树结构,每个节点及组件都会对应一个 fiber
  // FiberRoot 和 Root Fiber 会互相引用
  //通过以下代码找到 Fiber Root,对应着容器
  // document.querySelector('#root')._reactRootContainer._internalRoot
  const uninitializedFiber = createHostRootFiber(isConcurrent);
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;

  return root;
}

ReactWork详解

function ReactWork() {
  // 数组。存放所有在回调函数
  this._callbacks = null;
  // 已经在执行回调的标志位
  this._didCommit = false;
  // 重新绑定 this,以防取不到正确的 this
  this._onCommit = this._onCommit.bind(this);
}
// _onCommit方法,作用是将_callbacks中的回调挨个执行
ReactWork.prototype._onCommit = function(){}
// then方法,作用是将回调放入_callbacks中,如果_didCommit标志位为true, 则直接调用函数
ReactWork.prototype.then = function(){}

ReactBatch详解

function ReactBatch(root: ReactRoot) {
  const expirationTime = computeUniqueAsyncExpiration();
  this._expirationTime = expirationTime;
  this._root = root;
  this._next = null;
  this._callbacks = null;
  this._didComplete = false;
  this._hasChildren = false;
  this._children = null;
  this._defer = true;
}
// 调用updateContainerAtExpirationTime,返回reactworker
ReactWork.prototype.render = function(){}
// then方法,作用是将回调放入_callbacks中,如果_didComplete标志位为true, 则直接调用函数
ReactWork.prototype.then = function(){}
// Complete方法,作用是将_callbacks中的回调挨个执行
ReactWork.prototype._onComplete = function(){}
// commit方法,作用是找到最尾巴的ReactRoot,调用render方法(?所以这里其实是为了解决多次setstate重新渲染的问题?)
ReactWork.prototype.commit = function(){}

updateContainer详解

其实就是计算了expirationTime,然后调用updateContainerAtExpirationTime

updateContainerAtExpirationTime详解

先获取context。然后调用scheduleRootUpdate

scheduleRootUpdate详解

function scheduleRootUpdate(
  current: Fiber,
  element: ReactNodeList,
  expirationTime: ExpirationTime,
  callback: ?Function,
) {
    // 创建一个 update,就是内部有几个属性的对象
  const update = createUpdate(expirationTime);
  // Caution: React DevTools currently depends on this property
  // being called "element".
  update.payload = {element};
 // callback的检查
  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    warningWithoutStack(
      typeof callback === 'function',
      'render(...): Expected the last optional `callback` argument to be a ' +
        'function. Instead received: %s.',
      callback,
    );
    update.callback = callback;
  }
  // 执行 fiber 上的副作用(会将上个useEffect的return先执行再执行新的回调)
  flushPassiveEffects();
  // 把 update 入队,内部就是一些创建或者获取 queue(链表结构),然后给链表添加一个节点的操作
  // 这里面有两个队列,queue1 = fiber.updateQueue; queue2 = alternate.updateQueue;
  enqueueUpdate(current, update);
  // 就是scheduleUpdateOnFiber函数,
  scheduleWork(current, expirationTime);

  return expirationTime;
}
export function scheduleUpdateOnFiber(
  fiber: Fiber,
  expirationTime: ExpirationTime,
) {
  //  判断是否是无限循环update
  checkForNestedUpdates();
  // dev环境下的检查
  warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
  // 找到rootFiber并遍历更新子节点的expirationTime
  const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
  if (root === null) {
    warnAboutUpdateOnUnmountedFiberInDEV(fiber);
    return;
  }
  // NoWork表示无更新操作
  root.pingTime = NoWork;
  // 检查是否有高优先级任务打断当前正在执行的任务
  checkForInterruption(fiber, expirationTime);
  // 测试环境修改标志位记录更新
  recordScheduleUpdate();

  // 如果是同步任务的过期时间的话(Sync是最大的31位整型1073741823)
  if (expirationTime === Sync) {
    // 如果还未渲染
    if (workPhase === LegacyUnbatchedPhase) {
      // 批量更新时,render是要保持同步的,但布局的更新要延迟到批量更新的末尾才执行
      // 调用workLoop进行循环单元更新
      let callback = renderRoot(root, Sync, true);
      while (callback !== null) {
        callback = callback(true);
      }
    } else {
      // render后,立即执行调度任务
      scheduleCallbackForRoot(root, ImmediatePriority, Sync);
      // 当前没有update时
      if (workPhase === NotWorking) {
        // Flush the synchronous work now, wnless we're already working or inside
        // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
        // scheduleCallbackForFiber to preserve the ability to schedule a callback
        // without immediately flushing it. We only do this for user-initated
        // updates, to preserve historical behavior of sync mode.
        // 刷新同步任务队列
        flushImmediateQueue();
      }
    }
  } else {
    // TODO: computeExpirationForFiber also reads the priority. Pass the
    // priority as an argument to that function and this one.
    const priorityLevel = getCurrentPriorityLevel();
    if (priorityLevel === UserBlockingPriority) {
      // This is the result of a discrete event. Track the lowest priority
      // discrete update per root so we can flush them early, if needed.
      // TODO: 不太明白这里的意义
      if (rootsWithPendingDiscreteUpdates === null) {
        rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
      } else {
        const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
        if (
          lastDiscreteTime === undefined ||
          lastDiscreteTime > expirationTime
        ) {
          rootsWithPendingDiscreteUpdates.set(root, expirationTime);
        }
      }
    }
    scheduleCallbackForRoot(root, priorityLevel, expirationTime);
  }
}
export const scheduleWork = scheduleUpdateOnFiber;

接下来分析scheduleCallbackForRoot