深刻react-ReactDOM.render

前言

这篇主要就是介绍render的一个流程,只到调度为止,并无深刻到每一个点,涉及到的数据结构我会在下一篇专门列出来,这里只要知道建立了什么数据结构就能够了,我主要是按16.12.0这个版本讲的,今天发现已经更新到16.13.0了不过具体改了什么还没看react

render

export function render(element, container, callback) {
  // validate container

  return legacyRenderSubtreeIntoContainer(
    null,
    element,
    container,
    false,
    callback
  );
}
  • ReactDOM.render首先会调用上面这个函数
  • 该函数返回一个legacyRenderSubtreeIntoContainer函数调用的结果,传入5个参数
  • null:parentComponet
  • element:ReactDOM.render的第一个参数,前面讲API的时候讲过是一个ReactElement
  • container : ReactDOM.render的第二个参数,一个element容器
  • false: forceHydrate 服务端渲染用的,能够忽略
  • callback:ReactDOM.render的第三个参数,一个回调函数

legacyRenderSubtreeIntoContainer

function legacyRenderSubtreeIntoContainer(
  parentComponent,
  children,
  container,
  forceHydrate,
  callback
) {
  let root = container._reactRootContainer;
  let fiberRoot;
  if (!root) {
    // ReactDOMBlockingRoot实例,属性_internalRoot上挂载着fiberRootNode
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate
    );

    fiberRoot = root._internalRoot;

    unbatchedUpdates(() => {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  }
}
  • 首次渲染container._reactRootContainer确定为空
  • 直接进入到if(!root)中
  • 调用legacyCreateRootFromDOMContainer函数建立ReactDOMBlockingRoot赋值给root和container._reactRootContainer,这里也同时建立了fiberRootNode保存在_internalRoot属性中
  • 赋值fiberRootNode给fiberRoot
  • 调用unbatchedUpdates函数,取消批量更新

render.png

到这里是一个极简化的render,没有进入任何分支,下面咱们看下ReactDOMBlockingRoot实例的建立,以及挂载在该实例下的FiberRoot数据结构

legacyCreateRootFromDOMContainer

function legacyCreateRootFromDOMContainer(container, forceHydrate) {
  // 通常Hydrate的状况是在服务端渲染和预渲染(prerender-spa-plugin)
  const shouldHydrate =
    forceHydrate || shouldHydrateDueToLegacyHeuristic(container);

  if (!shouldHydrate) {
    let rootSibling;
    while ((rootSibling = container.lastChild)) {
      container.removeChild(rootSibling);
    }
  }

  return createLegacyRoot(
    container,
    shouldHydrate ? { hydrate: true } : undefined
  );
}
  • 这个函数其实就作了一件事,就是遍历删除container中的dom标签
  • 而后调用createLegacyRoot
  • hydrate这个东西能够彻底忽略

createLegacyRoot

export function createLegacyRoot(container, options = {}) {
  return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}
  • new了一个ReactDOMBlockingRoot 传递了三个参数
  • container:DOM ELement
  • options能够忽略,就是那个hydrate
  • LegacyRoot:一个常量,这里表示blockingRoot

ReactDOMBlockingRoot

class ReactDOMBlockingRoot {
  // container:domElement, tag:rootType, options: {hydrate:boolean}
  constructor(container, tag, options) {
    this._internalRoot = createRootImpl(container, tag, options);
  }
}
  • 一个ReactDOMBlockingRoot class
  • 在_internalRoot挂了 createRootImpl函数调用的结果,这就是以前讲render说到的ReactDOMBlockingRoot上有一个_internalRoot属性上面挂这fiberRootNode

createRootImpl

function createRootImpl(container, tag, options) {
  const hydrate = options !== null && options.hydrate === true;
  const hydrationCallbacks =
    (options != null && options.hydrationOptions) || null;

  // 拿到fiberRootNode
  const root = createContainer(container, tag, hydrate, hydrationCallbacks);
  // 讲fiberNode 挂载到container对象上
  markContainerAsRoot(root.current, container);

  return root;

}
  • 这里建立fiberRoot的分支有点多,再次调用了createContainer,这也是我将render和createFiberNode分开讲的缘由,不过不管他调用多少层最终目的就是返回一个FIberNode对象
  • markContainerAsRoot函数只是将最终返回的fiberRoot对象上的current属性挂载到了container上面

createContainer

export function createContainer(container, tag, hydrate, hydrationCallbacks) {
  return createFiberRoot(container, tag, hydrate, hydrationCallbacks);
}
  • 继续再调用下一个分支createFiberRoot

createFiberRoot

export function createFiberRoot(container, tag, hydrate, hydrationCallbacks) {
  // 建立fiber树root节点
  const root = new FiberRootNode(container, tag, hydrate);
  const uninitializedFiber = createHostRootFiber(tag);
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;
  initializeUpdateQueue(uninitializedFiber);

  return root;
}
  • 到这里其实就差很少结束了整个建立的过程
  • new FiberRootNode: 就是实例化了一个FiberRoot对象
  • createHostRootFiber:建立的是一个Fiber对象,这个Fiber也常常被叫RootFiber
  • 他会被挂到FiberRoot.current下面
  • initializeUpdateQueue:建立一个更新队列,挂载fiber.updateQueue下面
  • 最后进行返回
  • 这里总共会涉及到三个数据结构,分别是:FiberRoot,Fiber和updateQueue 我放到后面再讲,可是这个建立的流程到这里就算结束了,能够看下下面的图,对照源码再过一遍

createFiberRootNode.png

unbatchedUpdates

接下来是render的建立完fiberRoot后的另一个分支unbatchedUpdatesdom

const NoContext = /*                    */ 0b000000;
const BatchedContext = /*               */ 0b000001;
const EventContext = /*                 */ 0b000010;
const DiscreteEventContext = /*         */ 0b000100;
const LegacyUnbatchedContext = /*       */ 0b001000;
const RenderContext = /*                */ 0b010000;
const CommitContext = /*                */ 0b100000;

let executionContext = NoContext;

export function unbatchedUpdates(fn, a) {
  const prevExecutionContext = executionContext;

  // 去除executionContext上的BatchedContext
  executionContext &= ~BatchedContext;
  // 往executionContext上添加LegacyUnbatchedContext
  executionContext |= LegacyUnbatchedContext;
  // 进行回调
  try {
    return fn(a);
  } finally {
    executionContext = prevExecutionContext;
    if (executionContext === NoContext) {
      // 刷新同步任务队列
      flushSyncCallbackQueue();
    }
  }
}
  • 修改executionContext这个变量,让他含有LegacyUnbatchedContext,也就是非批量更新模式
  • 而后就是fn()执行传入的回调函数updateContainer
  • 最后会执行flushSyncCallbackQueue,刷新同步任务队列

updateContainer

/**
 *
 * @param {*} element render的第一个参数
 * @param {*} fiberRoot fiberRoot
 * @param {*} parentComponent 第一次渲染为null
 * @param {*} callback render的第三个参数,一个回调
 */
export function updateContainer(element, fiberRoot, parentComponent, callback) {
  const current = fiberRoot.current;
  // 这里获得的是到目前为止 react还能处理多少单位时间(1单位时间是10ms)
  const currentTime = requestCurrentTimeForUpdate();

  const suspenseConfig = requestCurrentSuspenseConfig();

  // 计算过时时间,主要用在concurrent模式时使用
  const expirationTime = computeExpirationForFiber(
    currentTime,
    current,
    suspenseConfig
  );

  // 建立一个更新链表
  const update = createUpdate(expirationTime, suspenseConfig);

  update.payload = { element };

  // 处理回调函数
  callback = callback === undefined ? null : callback;

  // 把建立的update添加到fiber的updateQueue上面
  enqueueUpdate(current, update);

  // 进入调度
  scheduleWork(current, expirationTime);

  return expirationTime;
}
  • 这里主要是计算expirationTime,会在后面调度中使用到
  • 建立一个更新链表的数据结构
  • enqueueUpdate: 将建立的更新链表添加到fiber的fiber的updateQueue中
  • scheduleWork: 进入调度

感受写的很差,太难写了,可是,看在我辛苦的份上动动小手点个赞哈哈,3q函数

相关文章
相关标签/搜索