📌

【React】レンダリングと、リロードって一緒じゃないの?って方へ

2023/09/09に公開

皆さんは、Reactのレンダリングについてどれだけ理解できていますか?
画面のリロード=レンダリングだと思っていませんか?そんな方のために今回はReactのレンダリングとはなにか?画面のリロードと何が違うのかを解説します。

ブラウザのレンダリング

そもそもレンダリングとはブラウザレンダリングのことを指していることを忘れてはいけません。レンダリングというのは、ブラウザがRenderツリーを元にブラウザ画面に描画することを言います。ではこのレンダリングがどのような仕組みなのか、詳しく見ていきましょう。

FPS

ブラウザは、FPSという単位を使って画面の動きを表現しています。これは、Frame Per Secondの略で、1秒間に何枚のフレームを表示すればいいかという単位になります。この枚数の多さが、画面の滑らかさに直結します。ブラウザでは60fpsが一般的に滑らかであると言える指標になります。

ちなみに60fpsというのは、16.6ms(約0.017秒)に1回画面の更新を行なっているということになります。意味わかんない数字です笑
つまり、この16.6ms内にすべての処理を抑える必要があります(パフォーマンス的に)。

ブラウザレンダリングの処理フロー

この16.6msに1回画面更新すればいいという話をしてきました。では、この16.6ms内でブラウザはどのような処理を行なっているのでしょうか見ていきましょう。
ブラウザで行われている処理内容は以下の5ステップが実行されています。

  1. Parse
  2. Style
  3. Layout
  4. Paint
  5. Composite

Parse

ここでは、HTMLとCSSの解析を行なっています。HTMLは**DOM Tree(DOMツリー)という形に変換され、CSSはStyle Rules**(スタイルルールズまたはCSSOM)という形に変換されます。この操作によって、ブラウザがHTMLとCSSを処理しやすくしています。

  • DOM:Document Of Model
  • CSSOM:CSS Of Model

DOMツリーについて
https://zenn.dev/sqer/articles/2d4def0f07bf04c5cc47

Style

この工程では、Perseで作成されたDOM TreeとStyle Rulesの紐付けが行われます。この紐付けで生成されるものを、**Render Treeと呼びます。各要素のことはRenderer**と言います。

Layout

この工程では、Style工程で作成されたRender Treeの各要素を画面にどのように表示するかを決定します。この工程で生成されるのは、**Layout Treeと呼び、それぞれの要素のことをLayout Block Flow**と呼びます。

※ここでCSSにおいて、**widthheightの決定が行われます。また、display: none;を指定した要素に関しては、このLayout Treeには含まれません。逆に、**visibility: hidden に関しては、Layout Treeには含まれますが、画面には表示されません。

Paint

この工程では、画面表示する要素のレイヤーを決定する工程になります。例えば、position: absolute;position: fixed;z-indexなどがその対象になります。このレイヤの順番を決定する命令のことを、Paint Records と呼びます。

Composite

この工程では、**Compositor Thread**という仕組みが動き、Paint工程ででた命令(Paint Records)をもとに、ピクセル単位で配色していきます。

※**transformopacity**はこの工程で決定される。そのため、横幅の変更や要素の非表示などは、transformopacityを使ってあげることで、パフォーマンス向上につながる(再計算の際にCompositeにしか関与しないため)

つまりHTML、CSS、JavaScriptを実際に画面に描画する事。これがブラウザにおけるレンダリングということになります。次にReactにおけるレンダリングについて見ていきます。

詳しく知りたい方はこちらから
https://blog.leap-in.com/lets-learn-how-to-browser-works/

Reactにおけるレンダリング(React18)

Reactにおけるレンダリングの仕組みは公式ドキュメントに詳しく記載されています。公式ドキュメントでは、レストランの仕組みを例に説明されていますが、ここではその例は使わずに書いていこうと思います。

Reactのレンダーには以下の3工程を経ることになります。

  1. レンダリングのトリガー
  2. コンポーネントのレンダリング
  3. DOMへのコミット

レンダリングのトリガー

Reactコンポーネントがレンダリングされる理由は以下の2つです。

  1. 初回レンダリング
  2. 状態変更によるレンダリング

初回レンダリング

アプリケーションが起動して一番最初にレンダリングをトリガーする必要があります。

状態変更によるレンダリング

コンポーネントの際レンダリングは、stateやpropsの状態更新によって発生します。これが再レンダリングの際のトリガーとなり、レンダリングをする準備フェーズに突入します。

コンポーネントのレンダリング

公式ドキュメントにも説明は書いてありますが、Reactのレンダリングはここが真骨頂というか、曖昧なところということになります。トリガーされたコンポーネントはこのフェーズでReactが呼び出す(仮想DOMを生成する)ことになります。

つまりReactにおいてレンダリングとは、Reactがコンポーネントを呼び出し、変更差分を再計算すること

もう少し詳しく書くと、コンポーネントのレンダリングというのは、トリガーされたコンポーネントを呼び出し(そのコンポーネントの仮想DOMを生成し)、その仮想DOMにおいて変更差分があるかどうかを計算し、差分を算出することになります。もちろんこの時初回レンダリングにおいては、トリガーによる変更差分はないので、ルートコンポーネントを呼び出すということになります。

仮想DOMについて知りたい方
https://qiita.com/seira/items/6767e222890c9890ecb9

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ツリーに反映させ、それをブラウザがレンダリングするということかなと思っています。すげえややこしい笑

参考

これ見るとめっちゃ勉強になる
https://youtu.be/355UuZWj75k?si=6ekGuJbTaa2BnwOl

Discussion