経験は何よりも饒舌

10年後に真価を発揮するかもしれないブログ 

Reactのコードを読む(2)

前回はReactDOMについてみたので、今回はrenderhello worldが描写されるところまでみていこうと思う。

wafuwafu13.hatenadiary.com

まずは、ReactDom.renderを出力してみる。

<!DOCTYPE html>
<html>
<head>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
</head>
<body>
</div>
<script>
    console.log(ReactDOM.render());
</script>
</body>
</html>

すると、以下のようなエラーが発生する。

Uncaught Error: Target container is not a DOM element.
    at Object.render (react-dom.development.js:26121)
    at index.html:11

エラーが発生しているのは26121行目で、!isValidContainer(container)trueになっている。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L26121

function render(element, container, callback) {
      if (!isValidContainer(container)) {
        {
          throw Error( "Target container is not a DOM element." );
        }
      }
      ...

isValidContainer関数は25932行目にある。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L25932

function isValidContainer(node) {
      return !!(node && (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE || node.nodeType === COMMENT_NODE && node.nodeValue === ' react-mount-point-unstable '));
}

今回は引数のnodeundefinedであるため、falseが返り、!isValidContainer(container)trueとなる。

では、以下のコードで実際にhello worldを表示させた状態でみていく。

<div id="root"></div>
<script>
    console.log(ReactDOM.render(
        "hello world",
        document.getElementById('root')
    ));
</script>
</body>
</html>

render関数の第二引数であるdocument.getElementById('root')nodeTypeを出力すると、1であった。

console.log(document.getElementById('root').nodeType); // 1

Node.nodeType1ということはつまりELEMENT_NODEであるから、isValidContainer関数はtrueを返す。

続いて、26126行目をみてみる。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L26126

var isModernRoot = isContainerMarkedAsRoot(container) && container._reactRootContainer === undefined;


まずは、isContainerMarkedAsRoot関数が定義されている10597行目をみてみる。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L10597

function isContainerMarkedAsRoot(node) {
    return !!node[internalContainerInstanceKey];
}

internalContainerInstanceKeyは、10583行目で生成されているrandomKeyを含む変数であり、10586行目に定義されている。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L10583
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L10586

var randomKey = Math.random().toString(36).slice(2);
...
var internalContainerInstanceKey = '__reactContainer$' + randomKey;

よって、最終的に例えばnode["__reactContainer$dgg96aihpip"]の存在が調べられ、falseが返り、26110行目の条件分岐は通らない。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L26110

if (isModernRoot) {
          error('You are calling ReactDOM.hydrate() on a container that was previously ' + 'passed to ReactDOM.createRoot(). This is not supported. ' + 'Did you mean to call createRoot(container, {hydrate: true}).render(element)?');
}

続く26133行目では、関数の返り値を返している。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L26133

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

legacyRenderSubtreeIntoContainer関数は26024行目に定義されており、その中で26032行目にrootが定義されている。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L26032

var root = container._reactRootContainer;

div #rootには_reactRootContainerは存在せず、rootundefinedになる。
よって、26035行目のif (!root)を通り、26037行目でrootが代入される。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L26037

root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);

legacyCreateRootFromDOMContainerは25983行目に定義されており、26011行目で関数の返り値を返している。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L26011

return createLegacyRoot(container, shouldHydrate ? {
    hydrate: true
} : undefined);

そのcreateLegacyRootは25929行目に定義されており、25930行目でインスタンスを返している。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L25930

return new ReactDOMBlockingRoot(container, LegacyRoot, options);

結果的に、rootは以下のような値になる。

_internalRoot: FiberRootNode
callbackNode: null
callbackPriority: 0
containerInfo: div#root
context: null
current: FiberNode {tag: 3, key: null, elementType: null, type: null, stateNode: FiberRootNode, …}
entangledLanes: 0
entanglements: (31) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
....

fiberRootの値はroot._internalRootであり、以下のようになる。

callbackNode: null
callbackPriority: 0
containerInfo: div#root
context: null
current: FiberNode {tag: 3, key: null, elementType: null, type: null, stateNode: FiberRootNode, …}
entangledLanes: 0
entanglements: (31) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
eventTimes: (31) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
expirationTimes: (31) [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]

このcontainerInfo: div#rootには、document.getElementById('root')で取得したDOMの情報が含まれている。

...
outerHTML: "<div id="root"></div>"
outerText: ""
ownerDocument: document
URL: "file:///Users/tagawahirotaka/Desktop/react-17-react-dom-17/index.html"
...

今までみてきたlegacyRenderSubtreeIntoContainerは、最終的に26070行目で関数の返り値を返している。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L26070

return getPublicRootInstance(fiberRoot);

getPublicRootInstanceは25515行目で定義されており、25527行目でNodeを返している。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L25527

return containerFiber.child.stateNode;

このcontainerFiber.child.stateNodeには、renderの第一引数で渡したhello worldnodeValuetextContentとして含まれている。

nodeName: "#text"
nodeType: 3
nodeValue: "hello world"
ownerDocument: document
parentElement: div#root
parentNode: div#root
previousElementSibling: null
previousSibling: null
textContent: "hello world"

このNodeが返されることにより、画面にhello worldが描写される。
途中の処理を結構飛ばしたので理解が曖昧だが、次はuseStateなどをみていきたい。

wafuwafu13.hatenadiary.com