Reactの描画処理が速い理由
Reactの描画処理では仮想DOMの採用にはじまり、高速化するために工夫がこらされている。
工夫の方向としては"処理が重いリアルなDOMの操作を極力抑える"というものでる。
まずはリアルDOMの処理内容を知り、そこからどういった点で仮想DOMの方が速いのか比較する。
リアルDOMの処理
リアルなDOMが行う処理は大きく4つある。
・DOMノードの作成
・ツリーへの追加と削除
・属性の変更
・イベント発行
DOMノードの作成
HTMLをパースする処理はC++で書かれている。
Gnomeというライブラリが使用されているらしいが情報が少なく確証はない。
C++では要素名(divやpなど)からHTML要素のコンストラクタを呼び出し、DOMのオブシェクトをC++のヒープ(動的に確保可能な領域)に確保、オブジェクトを初期化して返却する。
ツリーへの追加と削除
ここの処理が少し重い。
まずツリー状のDOMの生合成をチェックし、新しいツリーに差分を差し込む。
差分が追加され、親のDocumentが到達可能になった時点でscript要素であればスクリプトの実行、外部ファイルへのリンクであればダウンロードが行われる。
この時レンダリングも行われるがその中身もさらに3ステップに分解でき、レンダリングツリーの構成、レイアウト計算、レイアウト計算結果に基づく画面のペイントの順で処理が行われる。
このレンダリングツリーの構成が何度も走ってしまうことにより、無駄な更新処理が走ってしまうことがブラウザの描画処理の遅さとして懸念されていた点であり、これに対して遅延アタッチというレンダリングツリーの構成処理に入るタイミングを先送りにする対応が行われた。
属性の変更
img要素のsrc属性やlinkのhrefなどに変更があればコンテンツのロードが、style属性に変更があればスタイルの再計算を行う。
イベントの発行
イベントの伝播するパスをあらかじめ計算しておき、縦走してイベントハンドラを呼び出していく。
仮想DOMとの比較
仮装DOMはJavaScriptで世界が完結しているためJavaScriptからネイティブコードのC++とやりとりが必要ない。
また上記で紹介したリアルDOMのお仕事のうち、整合性のチェックや構造の変化に応じたイベントの発行やソースのロード、レンダリングも行わない。
この単純な処理量からしてもリアルDOMより仮装DOMの方が処理が早いと言われるのはうなづける。
しかしReact.jsでは更に軽量化の工夫が行われている。
React.jsの軽量化工夫
ReactElement
React.js における仮想 DOM の実体は ReactComponent とよばれるオブジェクトであるが、これはそこまで軽くない。
React.jsではrender()というメソッドがあり、これを通して各コンポーネントが必要とするサブツリーの形を調べる。
これに応じてReactは仮想DOMの実態である ReactComponent ではなく、ReactElementを返す。
ReactElement はコンポーネントのコンストラクタと引数を詰め合わせたステートレスなオブジェクトであり、コンポーネントのコンストラクタ呼び出しに必要な最低限の情報しかもっていないため軽い。
差分計算
render() が返したエレメントツリーと既存のコンポーネントツリーを比較しながら、フレームワークはコンポーネントの状態を更新する。 必要なら新しいコンポーネントインスタンスをつくり、余って要らないものは消す。既存のコンポーネントが使い回せるなら属性を上書きして済ます。
shouldComponentUpdate
ReactではコンポーネントのshouldComponentUpdate()を呼び、falseが返ってきたものに関しては以降の処理を実施しない。
これにより変更が及ばない部分のツリーは生成自体をまるごとスキップできる。
これらの工夫によりReactはJavaScriptの計算量を小さく抑えることに成功している。
さらにReactの工夫について詳細に書かれている記事はこちら
参考にした記事
リアルな DOM はなぜ遅いのか - steps to phantasien