LoginSignup
2
1

More than 3 years have passed since last update.

Reactでhello worldするまでを追ってみた

Posted at

Reactでhello worldされるまでのコードを追ってみました。
React.createElementとReactDOM.renderの流れを追いたいと思います。
間違っている箇所などコメントいただけるととても喜びます。

前提

  • 以下のような箇所は条件分岐されているような箇所は飛ばします。
    • __DEV__enableSchedulerTracing変数で条件分岐されている
  • コメントアウトは削除します。
  • 読みやすさのためにコードを省力している部分は、以下のようなコメントアウトをします。

// --- 省略した処理の説明 --- 

使用したもの

  • chromeのdevtoolsで確認しやすいようにsetTimeoutが挟まれてます。
index.dev.jsx
import React from 'react';
import ReactDOM from 'react-dom';

function App (props) {
  return (
    <div>
      Hello world
    </div>
  );
}

window.addEventListener('load', function () {
  setTimeout(function () {
    ReactDOM.render(
      <App/>,
      document.querySelector('#app-root')
    );
  }, 2000);
});
package.json
{
  "name": "experiment",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack-dev-server --progress --color --config ./webpack.config.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@babel/cli": "^7.2.3",
    "@babel/core": "^7.2.2",
    "@babel/preset-env": "^7.3.1",
    "@babel/preset-react": "^7.0.0",
    "babel-loader": "^8.0.5",
    "react": "^16.8.1",
    "react-dom": "^16.8.1",
    "webpack": "^4.29.3",
    "webpack-cli": "^3.2.3",
    "webpack-dev-server": "^3.1.14"
  }
}

評価されるコード

  • babelを通したコードです。
  • 以下の順番で実行をみていきます。
    • 1. React.createElement(App, null)
    • 2. ReactDOM.render(<App/>, document.querySelector('#app-root')
"use strict";

var _react = _interopRequireDefault(require("react"));

var _reactDom = _interopRequireDefault(require("react-dom"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function App(props) {
  return _react.default.createElement("div", null, "Hello world");
}

window.addEventListener('load', function () {
  setTimeout(function () {
    _reactDom.default.render( // <= 2
      _react.default.createElement(App, null), // <= 1
      document.querySelector('#app-root')
    );
  }, 2000);
});

1. React.createElement(App, null)

process

- createElement
    - return ReactElement

createElement

  • まずはReact.createElement実行していきます。
  • createElementは引数を元にごにょごにょしてパラメータを整えて、Elementの生成処理をReactElement関数に任せ、その返り値をそのまま返します。
  • 今回渡す引数にはpropsもchildrenもないので、ReactElementに渡す引数は非常に寂しいものになりました。
  • ReactCurrentOwner.currentという奴が出てきましたが、こちらはFiberのあたりを読んでみないとよくわからなそうです。


// type = App
// config = null
// children = undefined
function createElement (type, config, children) {

  // --- arrange parameter for ReactElement ---

  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );

}

ReactElement

  • 続いてReactElementの呼び出しです。こちらは基本的に受け取ったデータをオブジェクトに詰めることと、$$typeofというスペシャルなプロパティを定義するだけです。


// type,                     = App
// key,                      = null
// ref,                      = null
// self,                     = null
// source,                   = null
// ReactCurrentOwner.current = null
// props,                    = {}
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    $$typeof: REACT_ELEMENT_TYPE,
    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner,
  };

  // --- dev: awesome codes ---

  return element;
};
  • そして以下がcreateElementの今回の返り値になります。以降root-ReactElementと呼びます。
  • こちらは次のReactDOM.renderの第一引数にそのまま渡されます。

// root-ReactElement
{
  $$typeof: Symbol(react.element),
  type: App,
  key: null,
  ref: null,
  props: {},
  _owner: null,
}

2. ReactDOM.render(<App/>, document.querySelector('#app-root'))

  • 次にmountをするためにReactDOM.render関数を実行していきます。
  • こちらは結構処理が多いので、小分けにしていきたいと思います。

process


- ReactDOM.render
    - legacyRenderSubtreeIntoContainer
        - legacyCreateRootFromDOMContainer     <= 2-1
          - ReactRoot
        - ReactRoot.prototype.render           <= 2-2
            - updateContainer
              - scheduleRootUpdate
                - enqueueUpdate                <= 2-2-1
                - scheduleWork                 <= 2-2-2
                  ...
                  - performWork                <= 2-2-2-1
                      ...
                      - renderRoot             <= 2-2-2-1-1
                        ...
                        - performUnitOfWork    <= 2-2-2-1-1-1
                          ...
                          - beginWork          <= 2-2-2-1-1-2
                          - completeUnitOfWork <= 2-2-2-1-1-3
                      - completeRoot           <= 2-2-2-1-2

ReactDOM.render

  • まずはrender関数の実行です。第一引数には前節で生成されたReactElementのroot-ReactElementが渡されています。
  • 第3引数のcallbackは渡していないのでundefinedです。


  render(
    element: React$Element<any>, // root-ReactElement
    container: DOMContainer,     // div#app-root
    callback: ?Function,         // undefined
  ) {

    // --- dev: warning ---

    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      false,
      callback,
    );
  },

legacyRenderSubtreeIntoContainer

  • 次にlegacyRenderSubtreeIntoContainerです。
  • 初回のrenderのため、legacyCreateRootFromDOMContainerという関数を呼び出し、ReactRootインスタンスを生成し、インスタンスのlegacy_renderSubtreeIntoContainerメソッドを呼び出し、mountの処理をさらに進めていきます。


function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>, // null
  children: ReactNodeList,                     // root-ReactElement
  container: DOMContainer,                     // div#app-root
  forceHydrate: boolean,                       // false
  callback: ?Function,                         // undefined
) {
  // --- dev: warning ---

  let root: Root = (container._reactRootContainer: any);
  if (!root) {
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer( // <= 2-1
      container,
      forceHydrate,
    );

    // --- arrange callback ---

    unbatchedUpdates(() => {
      if (parentComponent != null) {
        // --- process when parentComponent is not null ---
      } else {
        root.render(children, callback);                                     // <= 2-2
      }
    });
  } else {

    // --- process when render is not initial ---

  }
  return getPublicRootInstance(root._internalRoot);
}

2-1 legacyCreateRootFromDOMContainer

  • 渡されたDOMのchildrenの掃除とReactRootの生成を担います。
  • 生成物をそのまま返します。

function legacyCreateRootFromDOMContainer(
  container: DOMContainer,
  forceHydrate: boolean,
): Root {
  const shouldHydrate =
    forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
  if (!shouldHydrate) {
    let warned = false;
    let rootSibling;
    while ((rootSibling = container.lastChild)) {

      // --- dev: warn on SSR related ---

      container.removeChild(rootSibling);
    }
  }

  // --- dev: warn on hydrate ---

  const isConcurrent = false;
  return new ReactRoot(container, isConcurrent, shouldHydrate);
}

2-1-1 ReactRoot

  • ReactRootというものを生成するために、react-reconcilerの方へ飛んだりします。
  • _internalRootプロパティにはFiberRootなる全てのFiberの親っぽいものが突っ込まれます。
  • こちらはcurrentのプロパティにHostRootのtagをもつFiberを持ちます。
    • こちらのFiberは以降root-HostRootFiberと呼びます。


function ReactRoot(
  container: DOMContainer, // 
  isConcurrent: boolean,
  hydrate: boolean,
) {
  const root = createContainer(container, isConcurrent, hydrate);
  this._internalRoot = root;
}


// react-reconciler from here

export function createContainer(
  containerInfo: Container,
  isConcurrent: boolean,
  hydrate: boolean,
): OpaqueRoot {
  return createFiberRoot(containerInfo, isConcurrent, hydrate);
}

export function createFiberRoot(
  containerInfo: any,
  isConcurrent: boolean,
  hydrate: boolean,
): FiberRoot {
  const uninitializedFiber = createHostRootFiber(isConcurrent);

  let root;
  if (enableSchedulerTracing) {
    // --- root with scheduler tracing ---
  } else {
    root = ({
      current: uninitializedFiber,
      containerInfo: containerInfo,
      pendingChildren: null,

      pingCache: null,

      earliestPendingTime: NoWork,
      latestPendingTime: NoWork,
      earliestSuspendedTime: NoWork,
      latestSuspendedTime: NoWork,
      latestPingedTime: NoWork,

      didError: false,

      pendingCommitExpirationTime: NoWork,
      finishedWork: null,
      timeoutHandle: noTimeout,
      context: null,
      pendingContext: null,
      hydrate,
      nextExpirationTimeToWorkOn: NoWork,
      expirationTime: NoWork,
      firstBatch: null,
      nextScheduledRoot: null,
    }: BaseFiberRootProperties);
  }

  uninitializedFiber.stateNode = root;

  return ((root: any): FiberRoot);
}

// root-HostRootFiber
FiberNode {
  actualDuration: 0
  actualStartTime: -1
  alternate: null
  child: null
  childExpirationTime: 0
  contextDependencies: null
  effectTag: 0
  elementType: null
  expirationTime: 0
  firstEffect: null
  index: 0
  key: null
  lastEffect: null
  memoizedProps: null
  memoizedState: null
  mode: 4
  nextEffect: null
  pendingProps: null
  ref: null
  return: null
  selfBaseDuration: 0
  sibling: null
  stateNode: root,
  tag: 3
  treeBaseDuration: 0
  type: null
  updateQueue: null
  _debugID: 1
  _debugIsCurrentlyTiming: false
  _debugOwner: null
  _debugSource: null
}
  • ここまできて、前述のlegacyRenderSubtreeIntoContainer内のrootの部分は以下となります。
  • 以降こちらのインスタンスをroot-ReactRoot、中身の_internalRootroot-internalRootと省略します。

// root-ReactRoot
ReactRoot {
  // root-internalRoot
  _internalRoot: {
    containerInfo: div#app-root,
    context: null,
    current: root-HostRootFiber,
    didError: false,
    earliestPendingTime: 0,
    earliestSuspendedTime: 0,
    expirationTime: 0,
    finishedWork: null,
    firstBatch: null,
    hydrate: false,
    interactionThreadID: 1,
    latestPendingTime: 0,
    latestPingedTime: 0,
    latestSuspendedTime: 0,
    memoizedInteractions: Set(0) {},
    nextExpirationTimeToWorkOn: 0,
    nextScheduledRoot: null,
    pendingChildren: null,
    pendingCommitExpirationTime: 0,
    pendingContext: null,
    pendingInteractionMap: Map(0) {},
    pingCache: null,
    timeoutHandle: -1,
  }
}

2-2 ReactRoot.prototype.render

  • 前節までで生成されたroot-ReactRootrenderメソッドを呼び出して、いよいよReactをmountしていきます。
  • unbatchedUpdatesという関数が出てきていますが、、今回のケースでは渡された関数をそのまま実行するだけです。
  • ReactWorkのインスタンスも生成して、その_onCommitメソッドをupdateContainerに渡しています。


ReactRoot.prototype.render = function(
  children: ReactNodeList, // root-ReactElement
  callback: ?() => mixed,  // undefined
): Work {
  const root = this._internalRoot;
  const work = new ReactWork();
  callback = callback === undefined ? null : callback;
  // --- dev: warning ---
  // --- set callback ---
  updateContainer(children, root, null, work._onCommit);
  return work;
};
  • updateContainerを実行します
  • computeExpirationForFiber関数を実行して、expirationTimeという処理が終わっているべき時間を求めています
    • こちらは以降root-expirationTimeと呼びます
  • scheduleRootUpdateを実行して更新処理を進めます。

https://github.com/facebook/react/blob/08e95543571eacbe88a03382adc9399607d53425/packages/react-reconciler/src/ReactFiberReconciler.js#L283
https://github.com/facebook/react/blob/08e95543571eacbe88a03382adc9399607d53425/packages/react-reconciler/src/ReactFiberReconciler.js#L162


export function updateContainer(
  element: ReactNodeList,                      // root-ReactElement
  container: OpaqueRoot,                       // root-internalRoot
  parentComponent: ?React$Component<any, any>, // null
  callback: ?Function,                         // work._onCommit
): ExpirationTime {
  const current = container.current;
  const currentTime = requestCurrentTime();
  const expirationTime = computeExpirationForFiber(currentTime, current);
  return updateContainerAtExpirationTime(
    element,
    container,
    parentComponent,
    expirationTime,
    callback,
  );
}

export function updateContainerAtExpirationTime(
  element: ReactNodeList,                      // root-ReactElement
  container: OpaqueRoot,                       // root-internalRoot
  parentComponent: ?React$Component<any, any>, // null
  expirationTime: ExpirationTime,              // root-expirationTime
  callback: ?Function,                         // work._onCommit
) {
  const current = container.current;

  // --- dev: warning ---

  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }

  return scheduleRootUpdate(current, element, expirationTime, callback);
}

// root-expirationTime
1073741823
  • scheduleRootUpdateを実行します。
  • やっていることは
    • updateを作成。
    • flushPassiveEffectsはなんかpassiveなeffectをflushするんでしょう
    • enqueueUpdatecurrentのFiberにupdateを盛る
    • scheduleWorkFiberupdateを実行する
    • と思われます


function scheduleRootUpdate(
  current: Fiber,                 // root-HostRootFiber
  element: ReactNodeList,         // root-ReactElement
  expirationTime: ExpirationTime, // root-expirationTime
  callback: ?Function,            // work._onCommit
) {
  // --- dev: warning ---

  const update = createUpdate(expirationTime);
  update.payload = {element};

  // --- arrange callback ---

  flushPassiveEffects();
  enqueueUpdate(current, update);        <= 2-2-1
  scheduleWork(current, expirationTime); <= 2-2-2

  return expirationTime;
}
  • 途中で出てくるupdateはこんな感じです。以降root-updateと呼びます。
  • こちらを保持するupdateQueueは連結リストであるので、nextのプロパティがありますね。

// root-update
{
  "expirationTime": root-expirationTime,
  "tag": 0,
  "payload": root-ReactElement,
  "callback": null,
  "next": null,
  "nextEffect": null
}

2-2-1 enqueueUpdate

  • enqueueUpdateを呼び出して、fiberupdateQueueに先ほど生成したupdateappendUpdateToQueueによって盛り盛りします。
  • root-HostRootFiberは生成されたばかりでしたので、updateQueueはここでcreateUpdateQueueによって作られます。


// fiber  = root-HostRootFiber
// update = root-update
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
  const alternate = fiber.alternate;
  let queue1;
  let queue2;
  if (alternate === null) {
    queue1 = fiber.updateQueue;
    queue2 = null;
    if (queue1 === null) {
      queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
    }
  } else {
    // --- process when there are two fiber nodes ---
  }
  if (queue2 === null || queue1 === queue2) {
    appendUpdateToQueue(queue1, update);
  } else {
    // --- process when there are two updateQueues ---
  }

  // --- dev: warning ---
}

// root-HostRootFiber.updateQueue
{
  "baseState": null,
  "firstUpdate": root-update,
  "lastUpdate": root-update,
  "firstCapturedUpdate": null,
  "lastCapturedUpdate": null,
  "firstEffect": null,
  "lastEffect": null,
  "firstCapturedEffect": null,
  "lastCapturedEffect": null
}

2-2-2 scheduleWork

  • scheduleRootUpdateの実行に戻りまして、次はscheduleWorkを実行します。ここからが長いです。。。
  • scheduleWorkToRootを実行して、rootをgetします。
  • 見つかったrootとそのrootがもつexpirationTimerequestWorkに渡します。
  • if文で出てくるnextRootはこのファイルが持つグローバル変数です。


// fiber          = root-HostRootFiber
// expirationTime = root-expirationTime
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
  const root = scheduleWorkToRoot(fiber, expirationTime);
  if (root === null) {
    // --- dev: warning ---
    return;
  }

  // --- process when interruption occuered

  markPendingPriorityLevel(root, expirationTime);
  if (
    !isWorking ||
    isCommitting ||
    nextRoot !== root
  ) {
    const rootExpirationTime = root.expirationTime;
    requestWork(root, rootExpirationTime);
  }
  // --- dev: warning ---
}
  • scheduleWorkToRootの実行です
  • 引数のexpirationTimeの方がroot-HostRootFiberのもつexpirationTimeよりも大きいので、この値がセットされます。
  • root-HostRootFiberreturnプロパティがnullかつtagHostRootの値を持っていますので、このfiberのもつstateNode(root-internalRoot)をFiberRootということにしてこの関数の実行を終了します。


// fiber          = root-HostRootFiber
// expirationTime = root-expirationTime
function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
  recordScheduleUpdate();

  // --- dev: warning ---

  // Update the source fiber's expiration time
  if (fiber.expirationTime < expirationTime) {
    fiber.expirationTime = expirationTime;
  }
  // --- process when there is alternate ---
  let node = fiber.return;
  let root = null;
  if (node === null && fiber.tag === HostRoot) {
    root = fiber.stateNode;
  } else {
    // --- process when its not root ---
  }

  // --- scheduler tracing related ---
  return root;
}
  • requestWorkの実行です
  • Syncという変数には31ビット符号あり整数の最大値が入っています。
  • root-expirationTimeにも同様の数値が入っています。


// isBatchingUpdates = false
// Sync              = 1073741823

// root           = root-ReactRoot
// expirationTime = root-expirationTime
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
  addRootToSchedule(root, expirationTime);
  if (isRendering) {
    return;
  }

  if (isBatchingUpdates) {
    // --- process when batchingUpdates ---
    return;
  }

  if (expirationTime === Sync) {
    performSyncWork();
  } else {
    scheduleCallbackWithExpirationTime(root, expirationTime);
  }
}
  • addRootToScheduleの実行です。ファイルのグローバル変数がごにょごにょされます。


// firstScheduledRoot = null
// lastScheduledRoot  = null

// root           = root-ReactRoot
// expirationTime = root-expirationTime
function addRootToSchedule(root: FiberRoot, expirationTime: ExpirationTime) {
  if (root.nextScheduledRoot === null) {
    root.expirationTime = expirationTime;
    if (lastScheduledRoot === null) {
      firstScheduledRoot = lastScheduledRoot = root;
      root.nextScheduledRoot = root;
    } else {
      // --- process when lastScheduledRoot has value ---
    }
  } else {
    // --- process when root has nextScheduledRoot property ---
  }
}
2-2-2-1 performWork
  • requestWorkに処理が戻りまして、performSyncWorkの実行です。こちらはperformWorkのためのシンプルなラッパーです
  • performWorkでは引数でデータが渡されなくなっているのですが、それはグローバル変数を頼りにしているからの模様です。

https://github.com/facebook/react/blob/08e95543571eacbe88a03382adc9399607d53425/packages/react-reconciler/src/ReactFiberScheduler.js#L2240
https://github.com/facebook/react/blob/08e95543571eacbe88a03382adc9399607d53425/packages/react-reconciler/src/ReactFiberScheduler.js#L2244


function performSyncWork() {
  performWork(Sync, false);
}

// minExpirationTime = Sync
// isYieldy          = false
function performWork(minExpirationTime: ExpirationTime, isYieldy: boolean) {
  findHighestPriorityRoot();

  if (isYieldy) {
    // --- process when isYieldy ---
  } else {
    while (
      nextFlushedRoot !== null &&
      nextFlushedExpirationTime !== NoWork &&
      minExpirationTime <= nextFlushedExpirationTime
    ) {
      performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
      findHighestPriorityRoot();
    }
  }

  if (isYieldy) {
    // --- process when isYieldy ---
  }
  // If there's work left over, schedule a new callback.
  if (nextFlushedExpirationTime !== NoWork) {
    scheduleCallbackWithExpirationTime(
      ((nextFlushedRoot: any): FiberRoot),
      nextFlushedExpirationTime,
    );
  }

  // Clean-up.
  finishRendering();
}
  • まずはfindHighestPriorityRootを実行してnextFlushedRootnextFlushedExpirationTimeのお世話をします
  • 実行後の値は以下です

nextFlushedRoot           = root-ReactRoot
nextFlushedExpirationTime = root-expirationTime
  • performWorkOnRootの実行です


// root           = root-ReactRoot
// expirationTime = root-expirationTime
// isYieldy       = false
function performWorkOnRoot(
  root: FiberRoot,
  expirationTime: ExpirationTime,
  isYieldy: boolean,
) {
  // --- warning ---
  isRendering = true;

  if (!isYieldy) {
    let finishedWork = root.finishedWork;
    if (finishedWork !== null) {
      // --- process ---
    } else {
      root.finishedWork = null;
      const timeoutHandle = root.timeoutHandle;
      if (timeoutHandle !== noTimeout) {
        root.timeoutHandle = noTimeout;
        cancelTimeout(timeoutHandle);
      }
      renderRoot(root, isYieldy);
      finishedWork = root.finishedWork;
      if (finishedWork !== null) {
        completeRoot(root, finishedWork, expirationTime);
      }
    }
  } else {
    // --- process when isYieldy ---
  }

  isRendering = false;
}
2-2-2-1-1 renderRoot
  • renderRootの実行です
  • DOMのreconcileや、propertyのセットなどこの関数の呼び出しの範囲内で実行されます。
  • 途中でグローバル変数nextUnitOfWorkを生成しています。
  • ごちゃごちゃしてますが、真ん中あたりのwookLoopがほぼ本体です。


// nextUnitOfWork = null

// root     = root-ReactRoot
// isYieldy = false
function renderRoot(root: FiberRoot, isYieldy: boolean): void {
  // --- warning ---

  flushPassiveEffects();

  isWorking = true;
  const previousDispatcher = ReactCurrentDispatcher.current;
  ReactCurrentDispatcher.current = ContextOnlyDispatcher;

  const expirationTime = root.nextExpirationTimeToWorkOn;

  if (
    expirationTime !== nextRenderExpirationTime ||
    root !== nextRoot ||
    nextUnitOfWork === null
  ) {
    // Reset the stack and start working from the root.
    resetStack();
    nextRoot = root;
    nextRenderExpirationTime = expirationTime;
    nextUnitOfWork = createWorkInProgress(
      nextRoot.current,
      null,
      nextRenderExpirationTime,
    );
    root.pendingCommitExpirationTime = NoWork;

    if (enableSchedulerTracing) {
      // --- process when schedulerTracing ---
    }
  }

  let prevInteractions: Set<Interaction> = (null: any);
  // --- process when schedulerTracing ---

  let didFatal = false;

  startWorkLoopTimer(nextUnitOfWork);

  do {
    try {
      workLoop(isYieldy);
    } catch (thrownValue) {
      // --- recovery process ---
    }
    break;
  } while (true);

  // --- process when schedulerTracing ---

  isWorking = false;
  ReactCurrentDispatcher.current = previousDispatcher;
  resetContextDependences();
  resetHooks();

  // Yield back to main thread.
  if (didFatal) {
    // --- process when fatal ---
    return;
  }

  if (nextUnitOfWork !== null) {
    const didCompleteRoot = false;
    stopWorkLoopTimer(interruptedBy, didCompleteRoot);
    interruptedBy = null;
    onYield(root);
    return;
  }

  const didCompleteRoot = true;
  stopWorkLoopTimer(interruptedBy, didCompleteRoot);
  const rootWorkInProgress = root.current.alternate;
  // --- warning ---

  nextRoot = null;
  interruptedBy = null;

  if (nextRenderDidError) {
    // --- process when error ---
  }

  if (isYieldy && nextLatestAbsoluteTimeoutMs !== -1) {
    // --- process when isYieldy ---
    return;
  }

  // Ready to commit.
  onComplete(root, rootWorkInProgress, expirationTime);
}

// root-WorkInProgress

FiberNode {
  actualDuration: 0
  actualStartTime: -1
  alternate: root-HostRootFiber
  child: null
  childExpirationTime: 0
  contextDependencies: null
  effectTag: 0
  elementType: null
  expirationTime: 1073741823
  firstEffect: null
  index: 0
  key: null
  lastEffect: null
  memoizedProps: null
  memoizedState: null
  mode: 4
  nextEffect: null
  pendingProps: null
  ref: null
  return: null
  selfBaseDuration: 0
  sibling: null
  stateNode: root,
  tag: 3
  treeBaseDuration: 0
  type: null
  updateQueue: root-HostRootFiber.updateQueueと同じ
}
2-2-2-1-1-1 performUnitOfWork
  • 何はともあれwookLoopを実行します。
  • performUnitOfWorkの返り値がnullになるまで回し続けるだけのお仕事です。
  • beginWorkです!こいつは大事やぞ(多分)
  • workLoopは3週します


function workLoop(isYieldy) {
  if (!isYieldy) {
    while (nextUnitOfWork !== null) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {
    // --- process when isYieldy ---
  }
}

// workInProgress = root-workInProgress
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
  const current = workInProgress.alternate;

  startWorkTimer(workInProgress);
  // --- dev: process ---

  // --- dev: set dev property to fiber ---

  let next;
  if (enableProfilerTimer) {
    // --- process when profilerTimer ---
  } else {
    next = beginWork(current, workInProgress, nextRenderExpirationTime);
    workInProgress.memoizedProps = workInProgress.pendingProps;
  }

  // --- dev: process ---

  if (next === null) {
    next = completeUnitOfWork(workInProgress);
  }

  ReactCurrentOwner.current = null;

  return next;
}
2-2-2-1-1-2 beginWork
  • beginWorkです。
  • 今回のworkInProgressが持っているタグはHostRootにあたります。


// current              = root-HostRootFiber
// workInProgress       = root-workInProgress
// renderExpirationTime = root-expirationTime
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  const updateExpirationTime = workInProgress.expirationTime;

  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;

    if (oldProps !== newProps || hasLegacyContextChanged()) {
      // --- process when change occured ---
    } else if (updateExpirationTime < renderExpirationTime) {
      // --- process when there is no pending work ---
      return bailoutOnAlreadyFinishedWork(
        current,
        workInProgress,
        renderExpirationTime,
      );
    }
  } else {
    didReceiveUpdate = false;
  }

  // Before entering the begin phase, clear the expiration time.
  workInProgress.expirationTime = NoWork;

  switch (workInProgress.tag) {
    // --- other cases ---

    case HostRoot:
      return updateHostRoot(current, workInProgress, renderExpirationTime);

    // --- other cases ---
  }
  // --- warning ---
}
  • updateHostRootの実行です。


// current              = root-HostRootFiber
// workInProgress       = root-workInProgress
// renderExpirationTime = root-expirationTime
function updateHostRoot(current, workInProgress, renderExpirationTime) {
  pushHostRootContext(workInProgress);
  const updateQueue = workInProgress.updateQueue;
  // --- warning ---
  const nextProps = workInProgress.pendingProps;
  const prevState = workInProgress.memoizedState;
  const prevChildren = prevState !== null ? prevState.element : null;
  processUpdateQueue(
    workInProgress,
    updateQueue,
    nextProps,
    null,
    renderExpirationTime,
  );
  const nextState = workInProgress.memoizedState;
  const nextChildren = nextState.element;
  // --- process when next/prev children are same ---
  const root: FiberRoot = workInProgress.stateNode;
  if (
    (current === null || current.child === null) &&
    root.hydrate &&
    enterHydrationState(workInProgress)
  ) {
    // --- other process ---
  } else {
    reconcileChildren(
      current,
      workInProgress,
      nextChildren,
      renderExpirationTime,
    );
    resetHydrationState();
  }
  // --- other process ---
}
  • processUpdateQueueを実行してうぷで!


// workInProgress       = root-workInProgress
// queue     = root-workInProgress.updateQueue
// props                = null
// instance             = null
// renderExpirationTime = root-expirationTime
export function processUpdateQueue<State>(
  workInProgress: Fiber,
  queue: UpdateQueue<State>,
  props: any,
  instance: any,
  renderExpirationTime: ExpirationTime,
): void {
  hasForceUpdate = false;

  queue = ensureWorkInProgressQueueIsAClone(workInProgress, queue);

  // --- dev ---

  // These values may change as we process the queue.
  let newBaseState = queue.baseState;
  let newFirstUpdate = null;
  let newExpirationTime = NoWork;

  // Iterate through the list of updates to compute the result.
  let update = queue.firstUpdate;
  let resultState = newBaseState;
  while (update !== null) {
    const updateExpirationTime = update.expirationTime;
    if (updateExpirationTime < renderExpirationTime) {
      // --- process with insufficient expirationTime ---
    } else {
      resultState = getStateFromUpdate(
        workInProgress,
        queue,
        update,
        resultState,
        props,
        instance,
      );
      const callback = update.callback;
      if (callback !== null) {
        workInProgress.effectTag |= Callback;
        // Set this to null, in case it was mutated during an aborted render.
        update.nextEffect = null;
        if (queue.lastEffect === null) {
          queue.firstEffect = queue.lastEffect = update;
        } else {
          queue.lastEffect.nextEffect = update;
          queue.lastEffect = update;
        }
      }
    }
    // Continue to the next update.
    update = update.next;
  }

  // --- process when queue has capturedUpdate ---

  if (newFirstUpdate === null) {
    queue.lastUpdate = null;
  }
  if (newFirstCapturedUpdate === null) {
    queue.lastCapturedUpdate = null;
  } else {
    workInProgress.effectTag |= Callback;
  }
  if (newFirstUpdate === null && newFirstCapturedUpdate === null) {
    newBaseState = resultState;
  }

  queue.baseState = newBaseState;
  queue.firstUpdate = newFirstUpdate;
  queue.firstCapturedUpdate = newFirstCapturedUpdate;

  workInProgress.expirationTime = newExpirationTime;
  workInProgress.memoizedState = resultState;

  // --- dev ---
}
  • getStateFromUpdateです。今回のtagUpdateState
  • 今回は実質update.payloadを新しいオブジェクトに展開するだけです。


// workInProgress       = root-workInProgress
// queue                = root-workInProgress.updateQueue
// update               = root-update
// prevState            = null
// props                = null
// instance             = null
function getStateFromUpdate<State>(
  workInProgress: Fiber,
  queue: UpdateQueue<State>,
  update: Update<State>,
  prevState: State,
  nextProps: any,
  instance: any,
): any {
  switch (update.tag) {
    // --- other cases ---
    case UpdateState: {
      const payload = update.payload;
      let partialState;
      if (typeof payload === 'function') {
        // --- process when payload is function ---
      } else {
        partialState = payload;
      }
      if (partialState === null || partialState === undefined) {
        return prevState;
      }
      return Object.assign({}, prevState, partialState);
    }
    // --- other cases ---
  }
  return prevState;
}

// root-workInProgress
FiberNode {
  actualDuration: 0
  actualStartTime: 59901.585000000065
  alternate: root-HostRootFiber
  child: null
  childExpirationTime: 0
  contextDependencies: null
  effectTag: 32
  elementType: null
  expirationTime: 0
  firstEffect: null
  index: 0
  key: null
  lastEffect: null
  memoizedProps: null
  memoizedState: {element: {}}
  mode: 4
  nextEffect: null
  pendingProps: null
  ref: null
  return: null
  selfBaseDuration: 0
  sibling: null
  stateNode: root-internalRoot
  tag: 3
  treeBaseDuration: 0
  type: null
  updateQueue: {baseState: {}, firstUpdate: null, lastUpdate: null, firstCapturedUpdate: null, lastCapturedUpdate: null, }
}

// root-workInProgress.updateQueue
{
  baseState: {element: root-ReactElement}
  firstCapturedEffect: null
  firstCapturedUpdate: null
  firstEffect: root-update
  firstUpdate: null
  lastCapturedEffect: null
  lastCapturedUpdate: null
  lastEffect: root-update
  lastUpdate: null
}
  • reconcileChildrenの実行で、Childrenを良きようにします。
  • childのプロパティの中身は後ほど示します。


// current              = root-HostRootFiber
// workInProgress       = root-workInProgress
// nextChildren         = root-ReactElement
// renderExpirationTime = root-expirationTime
export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderExpirationTime: ExpirationTime,
) {
  if (current === null) {
    // --- other process ---
  } else {
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderExpirationTime,
    );
  }
}
  • reconcileChildFibers
  • このwrapperはなんか最適化のためらしいけどもどういうことか・・・


export const reconcileChildFibers = ChildReconciler(true);

function ChildReconciler(shouldTrackSideEffects) {
  // --- other functions ---

  function placeSingleChild(newFiber: Fiber): Fiber {
    // This is simpler for the single child case. We only need to do a
    // placement for inserting new children.
    if (shouldTrackSideEffects && newFiber.alternate === null) {
      newFiber.effectTag = Placement;
    }
    return newFiber;
  }

  function reconcileSingleElement () {}

  // --- other functions ---

  // returnFiber       = root-HostRootFiber
  // currentFirstChild = null
  // newChild          = root-ReactElement
  // expirationTime    = root-expirationTime
  function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    expirationTime: ExpirationTime,
  ): Fiber | null {
    const isUnkeyedTopLevelFragment =
      typeof newChild === 'object' &&
      newChild !== null &&
      newChild.type === REACT_FRAGMENT_TYPE &&
      newChild.key === null;
    if (isUnkeyedTopLevelFragment) {
      newChild = newChild.props.children;
    }

    // Handle object types
    const isObject = typeof newChild === 'object' && newChild !== null;

    if (isObject) {
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE:
          return placeSingleChild(
            reconcileSingleElement(
              returnFiber,
              currentFirstChild,
              newChild,
              expirationTime,
            ),
          );
        // --- other case ---
      }
    }

    // --- other process ---
  }

  // --- other process ---
}

// returnFiber          = root-workInProgress
// currentFirstChild    = null
// element              = root-ReactElement
// renderExpirationTime = root-expirationTime
function reconcileSingleElement(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  element: ReactElement,
  expirationTime: ExpirationTime,
): Fiber {
  const key = element.key;
  let child = currentFirstChild;
  while (child !== null) {
    // --- process when child is not null ---
  }

  if (element.type === REACT_FRAGMENT_TYPE) {
    // --- process when fragment ---
  } else {
    const created = createFiberFromElement(
      element,
      returnFiber.mode,
      expirationTime,
    );
    created.ref = coerceRef(returnFiber, currentFirstChild, element);
    created.return = returnFiber;
    return created;
  }
}
  • createFiberFromElementを実行してFiberを生成します


// element        = root-ReactElement
// mode           = 4
// expirationTime = root-expirationTime
export function createFiberFromElement(
  element: ReactElement,
  mode: TypeOfMode,
  expirationTime: ExpirationTime,
): Fiber {
  let owner = null;
  // --- dev: process for debug ---
  const type = element.type;
  const key = element.key;
  const pendingProps = element.props;
  const fiber = createFiberFromTypeAndProps(
    type,
    key,
    pendingProps,
    owner,
    mode,
    expirationTime,
  );
  // --- dev: process for debug ---
  return fiber;
}


// type           = App
// key            = null
// pendingProps   = {}
// owner          = null
// mode           = 4
// expirationTime = root-expirationTime
export function createFiberFromTypeAndProps(
  type: any, // React$ElementType
  key: null | string,
  pendingProps: any,
  owner: null | Fiber,
  mode: TypeOfMode,
  expirationTime: ExpirationTime,
): Fiber {
  let fiber;

  let fiberTag = IndeterminateComponent;
  let resolvedType = type;
  if (typeof type === 'function') {
    if (shouldConstruct(type)) {
      // --- when its class Component ---
    }
  } else if (typeof type === 'string') {
    // --- when its HostComponent
  } else {
    // --- when else ---
  }

  fiber = createFiber(fiberTag, pendingProps, key, mode);
  fiber.elementType = type;
  fiber.type = resolvedType;
  fiber.expirationTime = expirationTime;

  return fiber;
}
  • 生成されたFiberはこちらです。reconcileChildrenの返り値でroot-workInProgress.childの中身になります。
  • 以降root-childFiberと呼びます

// root-childFiber

FiberNode {
  actualDuration: 0
  actualStartTime: -1
  alternate: null
  child: null
  childExpirationTime: 0
  contextDependencies: null
  effectTag: 2
  elementType: ƒ App(props)
  expirationTime: 1073741823
  firstEffect: null
  index: 0
  key: null
  lastEffect: null
  memoizedProps: null
  memoizedState: null
  mode: 4
  nextEffect: null
  pendingProps: {}
  ref: null
  return: root-workInProgress
  selfBaseDuration: 0
  sibling: null
  stateNode: null
  tag: 2
  treeBaseDuration: 0
  type: ƒ App(props)
  updateQueue: null
}
  • call履歴をはるか遡って、workLoopの2週目まで戻ってきます。

// nextUnitOfWork = root-childFiber

// isYiedly = false
function workLoop(isYieldy) {
  if (!isYieldy) {
    while (nextUnitOfWork !== null) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {
    // --- other process ---
  }
}
  • 以降差分のみになります

// current              = null
// workInProgress       = root-childFiber
// renderExpirationTime = root-expirationTime
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  const updateExpirationTime = workInProgress.expirationTime;

  if (current !== null) {
    // --- process when not null ---
  } else {
    didReceiveUpdate = false;
  }

  workInProgress.expirationTime = NoWork;

  switch (workInProgress.tag) {
    case IndeterminateComponent: {
      const elementType = workInProgress.elementType;
      return mountIndeterminateComponent(
        current,
        workInProgress,
        elementType,
        renderExpirationTime,
      );
    }
    // --- other cases ---
  }
}
  • mountIndeterminateComponentの実行です。
  • まずrenderWithHooksを実行してvalueの値を求めます。
  • その後再びのreconcileChildrenでchildrenの状態を求めます。


// _current             = null
// workInProgress       = root-childFiber
// Component            = App
// renderExpirationTime = root-expirationTime
function mountIndeterminateComponent(
  _current,
  workInProgress,
  Component,
  renderExpirationTime,
) {
  if (_current !== null) {
    // --- process when current not null ---
  }

  const props = workInProgress.pendingProps;
  const unmaskedContext = getUnmaskedContext(workInProgress, Component, false);
  const context = getMaskedContext(workInProgress, unmaskedContext);

  prepareToReadContext(workInProgress, renderExpirationTime);

  let value;

  if (__DEV__) {
    // --- dev ---
  } else {
    value = renderWithHooks(
      null,
      workInProgress,
      Component,
      props,
      context,
      renderExpirationTime,
    );
  }
  workInProgress.effectTag |= PerformedWork;

  if (
    typeof value === 'object' &&
    value !== null &&
    typeof value.render === 'function' &&
    value.$$typeof === undefined
  ) {
    // --- process for class component ---
  } else {
    workInProgress.tag = FunctionComponent;
    // --- dev ---
    reconcileChildren(null, workInProgress, value, renderExpirationTime);
    // --- dev ---
    return workInProgress.child;
  }
}
  • renderWithHooksです。
  • なんと!ついにこちらで関数呼び出しが行われております!こういうのが見たかった
  • 中盤あたりで出てくるrenderedWorkはなんのために宣言されているのだろうか・・・


// current                  = null
// workInProgress           = root-childFiber
// Component                = App
// props                    = {}
// context                  = {}
// nextRenderExpirationTime = root-expirationTime
export function renderWithHooks(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  props: any,
  refOrContext: any,
  nextRenderExpirationTime: ExpirationTime,
): any {
  renderExpirationTime = nextRenderExpirationTime;
  currentlyRenderingFiber = workInProgress;
  firstCurrentHook = nextCurrentHook =
    current !== null ? current.memoizedState : null;

  if (__DEV__) {
    // --- dev ---
  } else {
    ReactCurrentDispatcher.current =
      nextCurrentHook === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
  }

  let children = Component(props, refOrContext);

  if (didScheduleRenderPhaseUpdate) {
    do {
      // --- process when update ---
    } while (didScheduleRenderPhaseUpdate);

    renderPhaseUpdates = null;
    numberOfReRenders = 0;
  }

  // --- dev ---

  ReactCurrentDispatcher.current = ContextOnlyDispatcher;

  const renderedWork: Fiber = (currentlyRenderingFiber: any);

  renderedWork.memoizedState = firstWorkInProgressHook;
  renderedWork.expirationTime = remainingExpirationTime;
  renderedWork.updateQueue = (componentUpdateQueue: any);
  renderedWork.effectTag |= sideEffectTag;

  const didRenderTooFewHooks =
    currentHook !== null && currentHook.next !== null;

  renderExpirationTime = NoWork;
  currentlyRenderingFiber = null;

  firstCurrentHook = null;
  currentHook = null;
  nextCurrentHook = null;
  firstWorkInProgressHook = null;
  workInProgressHook = null;
  nextWorkInProgressHook = null;

  remainingExpirationTime = NoWork;
  componentUpdateQueue = null;
  sideEffectTag = 0;

  // --- warning ---

  return children;
}
  • 生成されたchildrenはこちら
  • 以降root-AppChildrenReactElementと呼びます

// root-AppChildrenReactElement

{
  $$typeof: Symbol(react.element)
  key: null
  props: {children: "Hello world"}
  ref: null
  type: "div"
}
  • 再びreconcileChildrenの実行です。

// current              = null
// workInProgress       = root-childFiber
// nextChildren         = root-AppChildrenReactElement
// renderExpirationTime = root-expirationTime
export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderExpirationTime: ExpirationTime,
) {
  if (current === null) {
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderExpirationTime,
    );
  } else {
    // --- other process ---
  }
}

// root-childFiber.child

FiberNode {
  actualDuration: 0
  actualStartTime: -1
  alternate: null
  child: null
  childExpirationTime: 0
  contextDependencies: null
  effectTag: 0
  elementType: "div"
  expirationTime: 1073741823
  firstEffect: null
  index: 0
  key: null
  lastEffect: null
  memoizedProps: null
  memoizedState: null
  mode: 4
  nextEffect: null
  pendingProps: {children: "Hello world"}
  ref: null
  return: root-workInProgress
  selfBaseDuration: 0
  sibling: null
  stateNode: null
  tag: 5
  treeBaseDuration: 0
  type: "div"
  updateQueue: null
}
  • 3週目のworkLoopです。

// nextUnitOfWork = root-childFiber.child

function workLoop(isYieldy) {
  if (!isYieldy) {
    while (nextUnitOfWork !== null) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {
    // --- process when isYieldy ---
  }
}

// current              = null
// workInProgress       = root-childFiber.child
// renderExpirationTime = root-expirationTime
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  const updateExpirationTime = workInProgress.expirationTime;

  if (current !== null) {
    // --- other process ---
  } else {
    didReceiveUpdate = false;
  }

  workInProgress.expirationTime = NoWork;

  switch (workInProgress.tag) {
    // --- other cases ---
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderExpirationTime);
    // --- other cases ---
  }
}


// current              = null
// workInProgress       = root-childFiber.child
// renderExpirationTime = root-expirationTime
function updateHostComponent(current, workInProgress, renderExpirationTime) {
  pushHostContext(workInProgress);

  if (current === null) {
    tryToClaimNextHydratableInstance(workInProgress);
  }

  const type = workInProgress.type;
  const nextProps = workInProgress.pendingProps;
  const prevProps = current !== null ? current.memoizedProps : null;

  let nextChildren = nextProps.children;
  const isDirectTextChild = shouldSetTextContent(type, nextProps);

  if (isDirectTextChild) {
    nextChildren = null;
  } else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
    // --- process when content change ---
  }

  markRef(current, workInProgress);

  if (
    renderExpirationTime !== Never &&
    workInProgress.mode & ConcurrentMode &&
    shouldDeprioritizeSubtree(type, nextProps)
  ) {
    workInProgress.expirationTime = workInProgress.childExpirationTime = Never;
    return null;
  }

  reconcileChildren(
    current,
    workInProgress,
    nextChildren,
    renderExpirationTime,
  );
  return workInProgress.child;
}
  • 上記までを実行してworkInProgress.childの中身はnullになっています。
  • のでこの3週目のperformUnitOfWorkで初めてcompleteUnitOfWorkが実行されます。

// workInProgress = root-childFiber.child
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
  // --- truncate ---

  let next;
  if (enableProfilerTimer) {
    // --- process ----
  } else {
    next = beginWork(current, workInProgress, nextRenderExpirationTime);
    workInProgress.memoizedProps = workInProgress.pendingProps;
  }

  // --- dev ---

  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    next = completeUnitOfWork(workInProgress);
  }

  ReactCurrentOwner.current = null;

  return next;
}
2-2-2-1-1-3 completeUnitOfWork


// workInProgress = root-childFiber.child
function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
  while (true) {
    const current = workInProgress.alternate;
    // --- dev ---

    const returnFiber = workInProgress.return;
    const siblingFiber = workInProgress.sibling;

    if ((workInProgress.effectTag & Incomplete) === NoEffect) {
      // --- dev ---
      nextUnitOfWork = workInProgress;
      if (enableProfilerTimer) {
        if (workInProgress.mode & ProfileMode) {
          startProfilerTimer(workInProgress);
        }
        nextUnitOfWork = completeWork(
          current,
          workInProgress,
          nextRenderExpirationTime,
        );
        if (workInProgress.mode & ProfileMode) {
          stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
        }
      } else {
        // --- other process ---
      }
      // --- dev ---
      stopWorkTimer(workInProgress);
      resetChildExpirationTime(workInProgress, nextRenderExpirationTime);
      // --- dev ---

      if (nextUnitOfWork !== null) {
        return nextUnitOfWork;
      }

      if (
        returnFiber !== null &&
        (returnFiber.effectTag & Incomplete) === NoEffect
      ) {
        if (returnFiber.firstEffect === null) {
          returnFiber.firstEffect = workInProgress.firstEffect;
        }
        if (workInProgress.lastEffect !== null) {
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
          }
          returnFiber.lastEffect = workInProgress.lastEffect;
        }

        const effectTag = workInProgress.effectTag;
        if (effectTag > PerformedWork) {
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = workInProgress;
          } else {
            returnFiber.firstEffect = workInProgress;
          }
          returnFiber.lastEffect = workInProgress;
        }
      }

      // --- dev ---

      if (siblingFiber !== null) {
        // --- when sibling ---
      } else if (returnFiber !== null) {
        workInProgress = returnFiber;
        continue;
      } else {
        // --- when reached root ---
      }
    } else {
      // --- other process ---
    }
  }

  return null;
}
  • App関数内の<div>Hello world</div>向けのcompleteWorkの実行です
  • ReactFiberHostConfigというファイルから読み込まれた関数を実行しているのですが、そのファイルの実態はReactDOMのpackageに含まれていて、参照のためにrollupがごにょごにょと頑張るらしいです。


// current              = null
// workInprogress       = root-childFiber.child
// renderExpirationTime = root-expirationTime
function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  const newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
    // --- other cases ---
    case HostComponent: {
      popHostContext(workInProgress);
      const rootContainerInstance = getRootHostContainer();
      const type = workInProgress.type;
      if (current !== null && workInProgress.stateNode != null) {
        // --- other process ---
      } else {
        if (!newProps) {
          // --- process when without props
          break;
        }

        const currentHostContext = getHostContext();
        let wasHydrated = popHydrationState(workInProgress);
        if (wasHydrated) {
          // --- process when hydrated ---
        } else {
          let instance = createInstance(
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          );

          appendAllChildren(instance, workInProgress, false, false);

          if (
            finalizeInitialChildren(
              instance,
              type,
              newProps,
              rootContainerInstance,
              currentHostContext,
            )
          ) {
            markUpdate(workInProgress);
          }
          workInProgress.stateNode = instance;
        }

        if (workInProgress.ref !== null) {
          markRef(workInProgress);
        }
      }
      break;
    }
    // --- other cases ---
  }

  return null;
}
  • createInstanceです
  • precacheFiberNodeupdateFiberPropsの実行がこちらで有りますが、どうも実DOMのプロパティにfiberとpropsの参照を持たせているようです。これは知らなかった。どっかで使われるんだろうか


// type                   = "div"
// props                  = { children: "Hello world"}
// rootContainerInstance  = div#app-root
// hostContext            = {namespace: "http://www.w3.org/1999/xhtml", ancestorInfo: {…}}
// internalInstanceHandle = root-childFiber.child
export function createInstance(
  type: string,
  props: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
  internalInstanceHandle: Object,
): Instance {
  let parentNamespace: string;
  if (__DEV__) {
    // --- dev ---
  } else {
    parentNamespace = ((hostContext: any): HostContextProd);
  }
  const domElement: Instance = createElement(
    type,
    props,
    rootContainerInstance,
    parentNamespace,
  );
  precacheFiberNode(internalInstanceHandle, domElement);
  updateFiberProps(domElement, props);
  return domElement;
}
  • ついにdocument.createElementが実行されております。こちらにおりましたか。。
  • 生成されたdomElementは以降root-AppChildDOMと呼びます


// type = 'div'
// props = { children: "Hello world" }
// rootContainerElement = div#app-root
// parentNameSpace = "http://www.w3.org/1999/xhtml"
export function createElement(
  type: string,
  props: Object,
  rootContainerElement: Element | Document,
  parentNamespace: string,
): Element {
  let isCustomComponentTag;

  const ownerDocument: Document = getOwnerDocumentFromRootContainer(
    rootContainerElement,
  );
  let domElement: Element;
  let namespaceURI = parentNamespace;
  if (namespaceURI === HTML_NAMESPACE) {
    namespaceURI = getIntrinsicNamespace(type);
  }
  if (namespaceURI === HTML_NAMESPACE) {
    // --- dev: warning ---

    if (type === 'script') {
      // --- when type is script ---
    } else if (typeof props.is === 'string') {
      // --- when its web components? ---
    } else {
      domElement = ownerDocument.createElement(type);
      // --- process for select tag ---
    }
  } else {
    // --- process for namespaced element ---
  }

  if (__DEV__) {
    // --- dev: warning ---
  }

  return domElement;
}
  • completeWorkの次の実行に移りましてappendAllChildrenの実行です
  • 今回のworkInProgressはchildがnullなのでwhile内は実行しません


// parent                = root-AppChildDOM
// workInProgress        = root-childFiber.child
// needsVisibilityToggle = false
// isHidden              = false
appendAllChildren = function(
    parent: Instance,
    workInProgress: Fiber,
    needsVisibilityToggle: boolean,
    isHidden: boolean,
  ) {
    let node = workInProgress.child;
    while (node !== null) {
      // --- process ---
    }
  };
  • completeWorkの次のfinalizeInitialChildrenの実行です


// domElement            = root-AppChildDOM
// type                  = "div"
// props                 = { children: "Hello world" }
// rootContainerInstance = div#app-root
// hostContext           = {namespace: "http://www.w3.org/1999/xhtml", ancestorInfo: {…}}
export function finalizeInitialChildren(
  domElement: Instance,
  type: string,
  props: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
): boolean {
  setInitialProperties(domElement, type, props, rootContainerInstance);
  return shouldAutoFocusHostComponent(type, props);
}


// domElement            = root-AppChildDOM
// type                  = "div"
// props                 = { children: "Hello world" }
// rootContainerInstance = div#app-root
export function setInitialProperties(
  domElement: Element,
  tag: string,
  rawProps: Object,
  rootContainerElement: Element | Document,
): void {
  const isCustomComponentTag = isCustomComponent(tag, rawProps);
  // --- dev: warning ---

  let props: Object;
  switch (tag) {
    // --- other cases ---
    default:
      props = rawProps;
  }

  assertValidProps(tag, props);

  setInitialDOMProperties(
    tag,
    domElement,
    rootContainerElement,
    props,
    isCustomComponentTag,
  );

  switch (tag) {
    // --- process input related tag ---
    default:
      if (typeof props.onClick === 'function') {
        trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
      }
      break;
  }
}


// tag                  = "div"
// domElement           = root-AppChildDOM
// rootContainerElement = div#app-root
// nextProps            = { children: "Hello world" }
// isCustomComponentTag = false
function setInitialDOMProperties(
  tag: string,
  domElement: Element,
  rootContainerElement: Element | Document,
  nextProps: Object,
  isCustomComponentTag: boolean,
): void {
  for (const propKey in nextProps) {
    if (!nextProps.hasOwnProperty(propKey)) {
      continue;
    }
    const nextProp = nextProps[propKey];
    if (propKey === STYLE) {
      // --- process when key is style ---
    } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
      // --- process when key is dangerouslySetInnerHtml ---
    } else if (propKey === CHILDREN) {
      if (typeof nextProp === 'string') {
        const canSetTextContent = tag !== 'textarea' || nextProp !== '';
        if (canSetTextContent) {
          setTextContent(domElement, nextProp);
        }
      } else if (typeof nextProp === 'number') {
        // --- process when props is number ---
      }
    } else if (
      // --- other cases ---
    }
  }
}
  • ここまできて、completeUnitOfWorkcompleteWorkの実行が終わり、その後何やかんやがあってcompleteUnitOfWorkのwhileをもう2週ほどするのですが、やんごとなき事情により割愛します
  • 呼び出し履歴をはるか戻ってrenderRootworkLoop呼び出し部分が完了してその続きです。
  • onCompleteを呼び出して、rootexpirationTimeの更新とfinishedWorkの更新をしています

// nextUnitOfWork = null

// root     = root-ReactRoot
// isYieldy = false
function renderRoot(root: FiberRoot, isYieldy: boolean): void {
  // --- previous process ---
  do {
    try {
      workLoop(isYieldy);
    } catch (thrownValue) {
      // --- recovery process ---
    }
    break;
  } while (true);

  // --- process when schedulerTracing ---

  isWorking = false;
  ReactCurrentDispatcher.current = previousDispatcher;
  resetContextDependences();
  resetHooks();

  // Yield back to main thread.
  if (didFatal) {
    // --- process when fatal ---
    return;
  }

  if (nextUnitOfWork !== null) {
    // --- process ---
    return;
  }

  const didCompleteRoot = true;
  stopWorkLoopTimer(interruptedBy, didCompleteRoot);
  const rootWorkInProgress = root.current.alternate;
  // --- warning ---

  nextRoot = null;
  interruptedBy = null;

  if (nextRenderDidError) {
    // --- process when error ---
  }

  if (isYieldy && nextLatestAbsoluteTimeoutMs !== -1) {
    // --- process when isYieldy ---
    return;
  }

  // Ready to commit.
  onComplete(root, rootWorkInProgress, expirationTime);
}
2-2-2-1-2 completeRoot
  • ついについにrenderRootの実行が終了しまして、performWorkOnRootに処理が戻ります
再喝

// root           = root-ReactRoot
// expirationTime = root-expirationTime
// isYieldy       = false
function performWorkOnRoot(
  root: FiberRoot,
  expirationTime: ExpirationTime,
  isYieldy: boolean,
) {
  // --- warning ---
  isRendering = true;

  if (!isYieldy) {
    let finishedWork = root.finishedWork;
    if (finishedWork !== null) {
      // --- process ---
    } else {
      // --- previous process ---
      renderRoot(root, isYieldy);
      finishedWork = root.finishedWork;
      if (finishedWork !== null) {
        completeRoot(root, finishedWork, expirationTime);
      }
    }
  } else {
    // --- process when isYieldy ---
  }

  isRendering = false;
}
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1