🎃

React操作によりアップデートからDomへ反映までの流れ①(0->1の変化)

2022/08/15に公開

背景

React初期マウント時からDom反映までFiberNodeツリーの動き
前回の続きです。画面をクリックしてからメモリのFiberツリーはどう変化するか、そしてdomへの反映はどうされるのかを図から説明します。

流れ

レンダリングフェーズ

  1. 初期状態

マウントしてからdomへ初期状態を反映して、メモリにあるFiberNodeツリーは上記なものになります。
唯一のfiberRootNodeは右側のフルーFiberNodeツリーをcurrentプロパティーで指しています。

  1. クリックしてからの挙動

この時に関数AppのFiberNodeのmemorizedStateのプロパティーに以下のupdate情報を打ち込んでいきます
詳しくはこちらの記事も参考
なぜReactのHookをIf分に入れてはいけないですか?
終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。

次にperformSyncWorkOnRootというreact-domのメソッドを呼び出し、再レンダリングを促します。

  1. 再レンダリング(rootFiber)
    これから行う再レンダリングは右側の既存のFiberNodeツリー(current)を元に、左側のFiberNodeツリー(workInProgress)を構築します。
    以下のステップです
  • fiberRootNode.currentをcurrent呼び名から指します
  • currentのalternateは左側のFiberNodeツリーの一番上のFiberNodeになります。それをworkInProgressという呼び名を指します。
  • workInProgressのchildプロパティーを右側のcurrentのchildプロパティーに指します。
  • App関数該当するFiberNodeのmemorizedState.queueのinterleavedプロパテーにあるアップデートオブジェクトをmemorizedState.queueのpendingプロパティーに移動します。

終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。

  1. 再レンダリング(workInProgress.childの作成)
    今までworkInProgress.childは右側のrootFiber.childを指していますが、workInProgress.childの差し先を改めて新しいFiberNodeにします。
    以下のステップです
  • 右側のrootFiber.childのタグとキーを元に新しいFiberNodeオブジェクトを作成します。
  • 新しく作成たFiberNodeと右側のrootFiber.childお互いにalternateプロパティーから指すようにします。
  • 左側のworkInProgressのchildプロパティーから新しく作成したFiberNodeに指すようにします。
  • 新しく作成したFiberNodeのreturnプロパティーからworkInProgressに指すようにします。

終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。

  1. 再レンダリング(workInProgress,currentの移動)
    workInProgress.childを作成したら、workInProgressを下に移動します。currentも下に移動します。
    終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。

  2. 再レンダリング(App関数の実行と子FiberNodeの作成)

  • App関数のFiberNodeに辿り着きました。まずはApp関数を実行します。関数を実行したら、最新のReact.Elementをもらっています。この関数の実行にはhookなどの内部動作があります。別途で説明します。
  • React.Elementのオブジェクトの中にpropsというプロパティーがあり、以下のように構成されてます。
children: {$$typeof: Symbol(react.element), type: 'p', key: null, ref: null, props: {…}, …}
className: "container"
onClick: () => setNum(num + 1)
  • current.childのタグとキーと前のステップ生成されたpropsを元に新しいFiberNodeを作成します。
    propsは新しく作成されたFiberNodeのpendingPropsに入れます。(更新差分を計算するため)
  • 新しく作成されたFiberNodeと右側のdiv.containerのFiberNodeお互いにalternateというプロパティーで指すようにします。
  • 新しく作成されたFiberNodeのchildプロパティーを右側のp.subContainerのFiberNodeに指すようにします
  • 新しく作成されたFiberNodeのreturnプロパティーを左側のApp関数のFiberNodeに指すようにします
  • 左側のworkInProgress(App関数のFiberNode)のchildプロパティーを新しく作成されたFiberNodeに指すようにします
    終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。
  1. 再レンダリング(workInProgress,currentの移動)
    workInProgress.childを作成したら、workInProgressを下に移動します。currentも下に移動します。
    終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。

  2. 再レンダリング(p.subContainerの作成FiberNode)
    div.containerのFiberNodeの辿り付いています。

  • pendingProps.children(React.Element)と右側のp.subContainerのFiberNodeのタグとキーを元に新しいFiberNodeを作成します。
  • 新しく作成されたFiberNodeと右側のp.subContainerのFiberNodeお互いにalternateというプロパティーで指すようにします。
  • 新しく作成されたFiberNodeのchildプロパティーをnull指すようにします。
  • 新しく作成されたFiberNodeのreturnプロパティーを左側のdiv.containerのFiberNodeに指すようにします。
  • 左側のdiv.containerのFiberNodeのchildプロパティーを新しく作成されたFiberNodeに指すようにします。
    終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。
  1. 再レンダリング(workInProgress,currentの移動)
    workInProgress.childを作成したら、workInProgressを下に移動します。currentも下に移動します。
    終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。
    workInProgressはp.subContainerのFiberNodeに辿り着きます。

※ここまではTopdownのdfs式Fiberツリーの更新は終わっています、これからはdownTop式でfiberツリーの更新をします

  1. p.subContainerのFiberNodeレンダリング(props差分計算)
    p.subContainerのFiberNodeのpenddingPropsとmemorizedPropsを元に、これからプロパティーの更新差分を計算します。
    p.subContainerのプロパティーは以下になります
children
className
onClick

その中に、childrenだけ変化があり。childrenは前の0からこれからの1になります。
diffPropertiesという関数によって、以下のアップデートをfiberNodeのupdateQueueというプロパティーに入れ込みます。

['children', '1']

終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。

  1. div.containerのFiberNodeレンダリング(props差分計算)
    workInProgressを一個上のdiv.containerのFiberNodeに移動し、fiberNodeのupdateQueueを計算します。
    div.containerのFiberNodeのpenddingPropsとmemorizedPropsを元に計算します。
    div.containerのFiberNodeのプロパティーは以下です。
children
className
onClick

その中に特に更新があるものはないから、計算できたupdateQueueは以下のものになります。

[]

計算できたupdateQueueをfiberNodeのupdateQueueというプロパティーに入れ込みます。
終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。

12.App関数のFiberNodeレンダリング
workInProgressを一個上のApp関数のFiberNodeに移動します。
特に何もしていないです。
終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。

  1. rootFiberのレンダリング
    workInProgressを一個上のrootFiberに移動します。
    特に何もしていないです。
    終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。

ここまでは、左側のFiberツリー構築完了し、レンダリングフェーズも完了です
これからcommitフェーズに入って、更新部分をdomへ反映します

commitフェーズ

トップのrootFiberから再帰で一番下のp.subContainerのFiberNodeにdfsします。そして、一番下にあるp.subContainerのFiberNodeから一番トップのrootFiberへバックトラッキングします。
バックトラキングにp.subContainerを経由する時に、レンダリングフェーズで計算できたupdateQueueを元に、更新部分(0->1への変化)をdomへ反映します。

最後fiberNodeRootのcurrentプロパティを右側のFiberツリーから左側のFiberツリーに指します。もう右側のFiberツリーは過去式になり、左側のFiberツリーは現在の最新のFiberツリーなので、currentプロパティから指すわけになります。
実際のソースコードは以下になります。

 root.current = finishedWork; // The next phase is the layout phase, where we call effects that read

https://www.youtube.com/watch?v=nMk5UGpd8Io&t=12s

Discussion