🌊

React初期マウント時からDom反映までFiberNodeツリーの動き

2022/08/12に公開

背景

画面をロードしてから、画面へ文字が反映されるまで、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の動作は大まかに二つに分けることができます。

  1. レンダーフェーズ: これはFiberNodeツリー周りの計算が行われています。
  2. commitフェーズ -> このフェーズのなかブラウザーDomの実際の操作が行われています。

レンダーフェーズ

  1. fiberRootを作成します
    最初はFiberNodeツリーの先祖を作ります。この先祖はFiberRootと言います。

  2. 前のステップから作成したfiberRootの子供FiberNodeを作成します。作成されたFiberNodeはrootFiberと呼びます。

  • このrootFiberのstateNodeというプロパーティは先祖のfiberRootを指すようにしています。
  • 先祖のfiberRootはcurrentというプロパーティは現在作られてきたrootFiberを指すようにしています。
  1. 第2ステップで作成したrootFiber(左)を元に同じFiberNodeを作成します。作成したFiberNodeもrootFiberになります。このrootFiberは右側に置き、workInProgressという呼び名を覚えて下さい。
  • 左のrootFiberと右のrootFiberをお互いにalternateというプロパティーを指しています。
  1. 右側のrootFiberを元に、子FiberNodeを生成します。生成した子FiberNodeはApp関数に該当するFiberNodeです。
  • 生成したFiberNodeはreturnというプロパティーでrootFiberを指しています。
  • 上のrootFiberはchildというプロパティーで生成したFiberNodeを指しています。
  1. そして、rootFiberから下にdfsして、生成した関数AppのFiberNodeにたどり,workInProgressは関数AppのFiberNodeに指します。

  2. App関数のFiberNodeを元に、さらにしたの子FiberNodeを作ります。作られたFiberNodeはclassName="container"のdivタグのFiberNodeです。

  • 生成したdivタグのFiberNodeはreturnというプロパティーで親のApp関数のFiberNodeを指しています
  • App関数のFiberNodeはchildというプロパティでdivタグのFiberNodeを指しています。
  1. そして、workInProgressは生成したdivタグのFiberNodeにdfsして、たどり着けます。

  2. 現在workInProgressはdivタグのFiberNodeに移動しました。divタグのFiberNodeを元に、子FiberNodeを作成します。今回className="subContainer"のpタグに該当するFiberNodeを作成しました。

  • 現在のdivのFiberNodeはchildというプロパティーから子FiberNodeを指すようにします。
  • 子FiberNodeはreturnというプロパティーで親のdivのFiberNodeを指すようにします。
  1. workInProgressを子FiberNodeに移動します。

  2. pタグのFiberNodeを元に、子FiberNodeを作ろうとしますが、pタグの下に生な文字列だけなので、pタグの子FiberNodeがnullになります。

  • 現在のpタグのFiberNodeはchildというプロパティから子FiberNode(null)を指すようにします。
  • workInProgressを下に移動しません。

ここまではトップのrootFiberから一番下の葉FiberNodeまでのdfsは終わります。

  1. 一番下の葉FiberNodeからトップのrootFiberに向かいながら、バックトラッキングします。
    バックトラッキングの時点はpタグのFiberNodeです。
  • pタグのFiberNodeはpendingPropsを元にpタグに該当するdom要素を作って、stateNodeというプロパティーに収めます。
  1. 次に上のdivタグのFiberNodeに遡ります。
  • 下のpタグのFiberNodeのstateNodeと現在のdivタグのFiberNodeを元に、divタグに属するdom要素を作ります。
  • 作ってdom要素をstateNodeというプロパティに収めます。
  1. 次に上のApp関数のFiberNodeに遡ります。
  • App関数のFiberNodeは特にdom要素を作ったりしていません。
  1. 次に上の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フェーズに行います。

https://www.youtube.com/watch?v=4Fn3dX-lxIM

PS: 真诚多一些,套路少一点

Discussion