7

前言

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

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后的另外一个分支unbatchedUpdates

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


accord
1.3k 声望187 粉丝

希望遇到一个公司,遇到一个团队,大家都愿意把code当作一种艺术去书写