Reactのレンダリングを理解する
Reactを触っていると、Rendering(レンダリング)という言葉はよく聞きますよね。このレンダリングというのが自分にはよくわからなかったので(なんとなく理解してるけど体系的に理解できていない)今回レンダリングの仕組みについて調べようと思い調べていたところとんでもない事実が判明した。
結論から言うとレンダリングという意味が、Reactにおけるレンダリングとブラウザにおけるレンダリングで全く意味が異なるということ。一般的にレンダリングというのはブラウザレンダリングという認識でいいかと思います。ではReactにおいてのレンダリングというのは何を指しているのでしょうか?
この記事では、フロントエンジニアにとって有益なブラウザレンダリングと、Reactにおけるレンダリングの違いについて公式ドキュメントをもとにできるだけ噛み砕いて説明していきたいと思います。
ブラウザのレンダリング
そもそもレンダリングとはブラウザレンダリングのことを指していることを忘れてはいけません。レンダリングというのは、ブラウザがRenderツリーを元にブラウザ画面に描画することを言います。ではこのレンダリングがどのような仕組みなのか、詳しく見ていきましょう。
FPS
ブラウザは、FPSという単位を使って画面の動きを表現しています。これは、Frame Per Secondの略で、1秒間に何枚のフレームを表示すればいいかという単位になります。この枚数の多さが、画面の滑らかさに直結します。ブラウザでは60fpsが一般的に滑らかであると言える指標になります。
ちなみに60fpsというのは、16.6ms(約0.017秒)に1回画面の更新を行なっているということになります。意味わかんない数字です笑
つまり、この16.6ms内にすべての処理を抑える必要があります(パフォーマンス的に)。
ブラウザレンダリングの処理フロー
この16.6msに1回画面更新すればいいという話をしてきました。では、この16.6ms内でブラウザはどのような処理を行なっているのでしょうか見ていきましょう。
ブラウザで行われている処理内容は以下の5ステップが実行されています。
- Parse
- Style
- Layout
- Paint
- Composite
Parse
ここでは、HTMLとCSSの解析を行なっています。HTMLは**DOM Tree
(DOMツリー)という形に変換され、CSSはStyle Rules
**(スタイルルールズまたはCSSOM)という形に変換されます。この操作によって、ブラウザがHTMLとCSSを処理しやすくしています。
- DOM:Document Of Model
- CSSOM:CSS Of Model
※DOMツリーについてはこちらを確認してください
Style
この工程では、Perseで作成されたDOM TreeとStyle Rulesの紐付けが行われます。この紐付けで生成されるものを、**Render Tree
と呼びます。各要素のことはRenderer
**と言います。
Layout
この工程では、Style工程で作成されたRender Treeの各要素を画面にどのように表示するかを決定します。この工程で生成されるのは、**Layout Tree
と呼び、それぞれの要素のことをLayout Block Flow
**と呼びます。
※ここでCSSにおいて、**width
やheight
の決定が行われます。また、display: none;
を指定した要素に関しては、このLayout Treeには含まれません。逆に、**visibility: hidden
に関しては、Layout Treeには含まれますが、画面には表示されません。
Paint
この工程では、画面表示する要素のレイヤーを決定する工程になります。例えば、position: absolute;
やposition: fixed;
やz-index
などがその対象になります。このレイヤの順番を決定する命令のことを、Paint Records
と呼びます。
Composite
この工程では、**Compositor Thread
**という仕組みが動き、Paint工程ででた命令(Paint Records)をもとに、ピクセル単位で配色していきます。
※**transform
やopacity
**はこの工程で決定される。そのため、横幅の変更や要素の非表示などは、transform
やopacity
を使ってあげることで、パフォーマンス向上につながる(再計算の際にCompositeにしか関与しないため)
つまりHTML、CSS、JavaScriptを実際に画面に描画する事。これがブラウザにおけるレンダリングということになります。次にReactにおけるレンダリングについて見ていきます。
参考:詳しく知りたい方はこちらから
フロントエンジニアなら知っておきたいブラウザレンダリングの仕組みをわかりやすく解説! | Tech Blog
Reactにおけるレンダリング(React18)
Reactにおけるレンダリングの仕組みは公式ドキュメントに詳しく記載されています。公式ドキュメントでは、レストランの仕組みを例に説明されていますが、ここではその例は使わずに書いていこうと思います。
Reactのレンダーには以下の3工程を経ることになります。
- レンダリングのトリガー
- コンポーネントのレンダリング
- DOMへのコミット
レンダリングのトリガー
Reactコンポーネントがレンダリングされる理由は以下の2つです。
- 初回レンダリング
- 状態変更によるレンダリング
初回レンダリング
アプリケーションが起動して一番最初にレンダリングをトリガーする必要があります。
状態変更によるレンダリング
コンポーネントの際レンダリングは、stateやpropsの状態更新によって発生します。これが再レンダリングの際のトリガーとなり、レンダリングをする準備フェーズに突入します。
コンポーネントのレンダリング
公式ドキュメントにも説明は書いてありますが、Reactのレンダリングはここが真骨頂というか、曖昧なところということになります。トリガーされたコンポーネントはこのフェーズでReactが呼び出す(仮想DOMを生成する)ことになります。
つまりReactにおいてレンダリングとは、Reactがコンポーネントを呼び出し、変更差分を再計算すること
もう少し詳しく書くと、コンポーネントのレンダリングというのは、トリガーされたコンポーネントを呼び出し(そのコンポーネントの仮想DOMを生成し)、その仮想DOMにおいて変更差分があるかどうかを計算し、差分を算出することになります。もちろんこの時初回レンダリングにおいては、トリガーによる変更差分はないので、ルートコンポーネントを呼び出すということになります。
*仮想DOMについて知りたい方
【React】仮想DOMって何!?コンポーネントのレンダリングと描画について理解しよう! - Qiita
DOMへのコミット
ここまでの操作でReactは仮想DOMの生成を行い、レンダリングによって仮想DOM内の差分を計算することがわかりました。このフェーズでは仮想DOMから本来のDOMへの反映を行います。
- 初回レンダリング:appendChild() DOM APIを使用して作成したすべてのDOMノードをDOMにコミットします。
- 再レンダリング:レンダリング時に計算され、差分が見つかったDOMノードのみコミットします。
つまりReactがDOMノードを変更するのは、直前の計算結果と再計算時にDOMノードに違いがある場合だけということになります
逆に言えば、レンダリングによって再計算が走ったとしても、DOMノードに変更差分がない場合コミットは行われないため、DOMに変更が発生しないため、ブラウザレンダリングは起こらないということが言えます。
結論
ブラウザレンダリングとReactにおけるレンダリングというのは全く違うことがわかります。まとめると下記のようなことかなと思います。
- ブラウザレンダリング:HTMLのDOMツリーを元に画面表示を行うこと
- Reactレンダリング:Reactが変更差分のあるコンポーネントを呼び出すこと
Reactがコンポーネントをレンダリングし、変更があるコンポーネント内の変更が発生したDOMノードをDOMツリーに反映させ、それをブラウザがレンダリングするということかなと思っています。すげえややこしい笑
Discussion