React初期マウント時からDom反映までFiberNodeツリーの動き
背景
画面をロードしてから、画面へ文字が反映されるまで、Reactはいったい裏側でなにかされたのかを説明します。
Reactの仕組みについて初回する記事はたくさんありますが、ほとんど概要だけの紹介なので、解像度を高めるためには実際のソースコードを潜って見に行くしかないですね。
この記事はまず、図からメモリでなにか行われてあるかを紹介します。また別の記事もしくはyoutubenお動画で実際のコードをdebugしながら、メモリの行動を紹介する予定です。
バージョン情報
"react": "18.2.0",
"react-dom": "18.2.0"
コード情報
- index.tsx
import { createRoot } from 'react-dom/client';
import App from './App';
const container = document.getElementById('root');
if (container != null) {
const root = createRoot(container); // createRoot(container!) if you use TypeScript
root.render(<App />);
}
- app.tsx
import { useState } from 'react';
function App() {
const [num,setNum] = useState(0)
return (
<div className="container" onClick={() => setNum(num + 1)}>
<p className='subContainer'>
{num}
</p>
</div>
);
}
export default App;
流れ
Reactの動作は大まかに二つに分けることができます。
- レンダーフェーズ: これはFiberNodeツリー周りの計算が行われています。
- commitフェーズ -> このフェーズのなかブラウザーDomの実際の操作が行われています。
レンダーフェーズ
-
fiberRootを作成します
最初はFiberNodeツリーの先祖を作ります。この先祖はFiberRootと言います。
-
前のステップから作成したfiberRootの子供FiberNodeを作成します。作成されたFiberNodeはrootFiberと呼びます。
- このrootFiberのstateNodeというプロパーティは先祖のfiberRootを指すようにしています。
- 先祖のfiberRootはcurrentというプロパーティは現在作られてきたrootFiberを指すようにしています。
- 第2ステップで作成したrootFiber(左)を元に同じFiberNodeを作成します。作成したFiberNodeもrootFiberになります。このrootFiberは右側に置き、workInProgressという呼び名を覚えて下さい。
- 左のrootFiberと右のrootFiberをお互いにalternateというプロパティーを指しています。
- 右側のrootFiberを元に、子FiberNodeを生成します。生成した子FiberNodeはApp関数に該当するFiberNodeです。
- 生成したFiberNodeはreturnというプロパティーでrootFiberを指しています。
- 上のrootFiberはchildというプロパティーで生成したFiberNodeを指しています。
-
そして、rootFiberから下にdfsして、生成した関数AppのFiberNodeにたどり,workInProgressは関数AppのFiberNodeに指します。
-
App関数のFiberNodeを元に、さらにしたの子FiberNodeを作ります。作られたFiberNodeはclassName="container"のdivタグのFiberNodeです。
- 生成したdivタグのFiberNodeはreturnというプロパティーで親のApp関数のFiberNodeを指しています
- App関数のFiberNodeはchildというプロパティでdivタグのFiberNodeを指しています。
-
そして、workInProgressは生成したdivタグのFiberNodeにdfsして、たどり着けます。
-
現在workInProgressはdivタグのFiberNodeに移動しました。divタグのFiberNodeを元に、子FiberNodeを作成します。今回className="subContainer"のpタグに該当するFiberNodeを作成しました。
- 現在のdivのFiberNodeはchildというプロパティーから子FiberNodeを指すようにします。
- 子FiberNodeはreturnというプロパティーで親のdivのFiberNodeを指すようにします。
-
workInProgressを子FiberNodeに移動します。
-
pタグのFiberNodeを元に、子FiberNodeを作ろうとしますが、pタグの下に生な文字列だけなので、pタグの子FiberNodeがnullになります。
- 現在のpタグのFiberNodeはchildというプロパティから子FiberNode(null)を指すようにします。
- workInProgressを下に移動しません。
ここまではトップのrootFiberから一番下の葉FiberNodeまでのdfsは終わります。
- 一番下の葉FiberNodeからトップのrootFiberに向かいながら、バックトラッキングします。
バックトラッキングの時点はpタグのFiberNodeです。
- pタグのFiberNodeはpendingPropsを元にpタグに該当するdom要素を作って、stateNodeというプロパティーに収めます。
- 次に上のdivタグのFiberNodeに遡ります。
- 下のpタグのFiberNodeのstateNodeと現在のdivタグのFiberNodeを元に、divタグに属するdom要素を作ります。
- 作ってdom要素をstateNodeというプロパティに収めます。
- 次に上のApp関数のFiberNodeに遡ります。
- App関数のFiberNodeは特にdom要素を作ったりしていません。
- 次に上のrootFiberに遡ります。
- 特にdom要素を作ったりしていません。
ここまではFiberNodeツリーのdfsとバックトラッキングは完了です。
Reactの第一レンダーフェイズは主に上記なFiberNodeツリーの構築をしました。
commitフェーズ
実際の状況はもっと複雑ですが、この段階では、一言っていうと、以下のdom容器に
<div id="root"></div>
APP関数のchildであるdivタグのFiberNodeのstateNodeのプロパーティに収めたdom要素を挿入します。
ここのappendChildToContainer
はブラウザーのdom操作のオリジナルAPIである。
domへの反映をした後、fiberRootNodeはcurrentプロパテーを左のfiberツリーから右のfiberツリーに指すようにします。
まとめ
- マウントの時に、FiberNodeツリー構築はレンダーフェーズに行います。
- マウントの時に、dom要素を事前に用意しています。それはレンダーフェーズのバックトラッキングの時に行います。
- マウントの時に、dom要素の挿入はcommitフェーズに行います。
PS: 真诚多一些,套路少一点
Discussion