React操作によりアップデートからDomへ反映までの流れ①(0->1の変化)
背景
React初期マウント時からDom反映までFiberNodeツリーの動き
前回の続きです。画面をクリックしてからメモリのFiberツリーはどう変化するか、そしてdomへの反映はどうされるのかを図から説明します。
流れ
レンダリングフェーズ
- 初期状態
マウントしてからdomへ初期状態を反映して、メモリにあるFiberNodeツリーは上記なものになります。
唯一のfiberRootNodeは右側のフルーFiberNodeツリーをcurrentプロパティーで指しています。
- クリックしてからの挙動
この時に関数AppのFiberNodeのmemorizedStateのプロパティーに以下のupdate情報を打ち込んでいきます
詳しくはこちらの記事も参考
なぜReactのHookをIf分に入れてはいけないですか?
終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。
次にperformSyncWorkOnRootというreact-domのメソッドを呼び出し、再レンダリングを促します。
- 再レンダリング(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ツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。
- 再レンダリング(workInProgress.childの作成)
今までworkInProgress.childは右側のrootFiber.childを指していますが、workInProgress.childの差し先を改めて新しいFiberNodeにします。
以下のステップです
- 右側のrootFiber.childのタグとキーを元に新しいFiberNodeオブジェクトを作成します。
- 新しく作成たFiberNodeと右側のrootFiber.childお互いにalternateプロパティーから指すようにします。
- 左側のworkInProgressのchildプロパティーから新しく作成したFiberNodeに指すようにします。
- 新しく作成したFiberNodeのreturnプロパティーからworkInProgressに指すようにします。
終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。
-
再レンダリング(workInProgress,currentの移動)
workInProgress.childを作成したら、workInProgressを下に移動します。currentも下に移動します。
終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。
-
再レンダリング(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ツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。
-
再レンダリング(workInProgress,currentの移動)
workInProgress.childを作成したら、workInProgressを下に移動します。currentも下に移動します。
終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。
-
再レンダリング(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ツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。
- 再レンダリング(workInProgress,currentの移動)
workInProgress.childを作成したら、workInProgressを下に移動します。currentも下に移動します。
終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。
workInProgressはp.subContainerのFiberNodeに辿り着きます。
※ここまではTopdownのdfs式Fiberツリーの更新は終わっています、これからはdownTop式でfiberツリーの更新をします
- p.subContainerのFiberNodeレンダリング(props差分計算)
p.subContainerのFiberNodeのpenddingPropsとmemorizedPropsを元に、これからプロパティーの更新差分を計算します。
p.subContainerのプロパティーは以下になります
children
className
onClick
その中に、childrenだけ変化があり。childrenは前の0からこれからの1になります。
diffProperties
という関数によって、以下のアップデートをfiberNodeのupdateQueueというプロパティーに入れ込みます。
['children', '1']
終わったら、メモリにあるFiberNodeツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。
- 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ツリーの一部分は以下のようになります。緑の部分は変化が行われた部分です。
- 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
Discussion