Reactでhello worldされるまでのコードを追ってみました。
React.createElementとReactDOM.renderの流れを追いたいと思います。
間違っている箇所などコメントいただけるととても喜びます。
前提
- 以下のような箇所は条件分岐されているような箇所は飛ばします。
-
__DEV__
やenableSchedulerTracing
変数で条件分岐されている
-
- コメントアウトは削除します。
- 読みやすさのためにコードを省力している部分は、以下のようなコメントアウトをします。
// --- 省略した処理の説明 ---
使用したもの
- chromeのdevtoolsで確認しやすいようにsetTimeoutが挟まれてます。
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);
});
{
"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を通したコードです。
- 以下の順番で実行をみていきます。
-
- React.createElement(App, null)
-
- 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
、中身の_internalRoot
をroot-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-ReactRoot
のrender
メソッドを呼び出して、いよいよ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するんでしょう -
enqueueUpdate
でcurrent
のFiberにupdate
を盛る -
scheduleWork
でFiber
のupdate
を実行する - と思われます
-
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
を呼び出して、fiber
のupdateQueue
に先ほど生成したupdate
をappendUpdateToQueue
によって盛り盛りします。 -
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がもつ
expirationTime
をrequestWork
に渡します。 - 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-HostRootFiber
のreturn
プロパティがnull
かつtag
にHostRoot
の値を持っていますので、この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
を実行してnextFlushedRoot
とnextFlushedExpirationTime
のお世話をします - 実行後の値は以下です
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
です。今回のtag
はUpdateState
。 - 今回は実質
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
です -
precacheFiberNode
とupdateFiberProps
の実行がこちらで有りますが、どうも実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 ---
}
}
}
- ここまできて、
completeUnitOfWork
のcompleteWork
の実行が終わり、その後何やかんやがあってcompleteUnitOfWork
のwhileをもう2週ほどするのですが、やんごとなき事情により割愛します - 呼び出し履歴をはるか戻って
renderRoot
のworkLoop
呼び出し部分が完了してその続きです。 -
onComplete
を呼び出して、root
のexpirationTime
の更新と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;
}