深刻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函數

相關文章
相關標籤/搜索