経緯
DOMについて調べてた時に気づいたので、備忘録として。
(この備忘録ではReactベースで書いてます)
結論
仮想DOMの真のメリットは「パフォーマンスが良いこと」ではなく、「宣言的UIの実現と現実的なパフォーマンスを両立できること」にある。
命令的UIと宣言的UIってなんだろう...
命令的UI
「イベント・変更が発生するたびに、どのように処理を実行し、状態を反映するのか」を記述する必要がある
const checkbox = document.querySelector(".checkbox");
const button = document.querySelector(".button");
checkbox.addEventListener("click", () => {
if (checkbox.checked === true) {
button.disabled = true;
} else {
button.disabled = false;
}
});
宣言的UI
状態に応じて何を行うかは問わず、「この状態であればこのように表示する」ということをあらかじめ宣言しておく。
<button disabled={is_checked ? true : false}>ボタン</button>
宣言的UIにするメリット
- 状態に応じて求めているUI表示を一緒にしているので、一目で把握できる
- 圧倒的にコード量が減らせる
Reactでの仮想DOM
あるツリーを別のものに変換するための最小限の操作を求めるというアルゴリズム問題については、いくつかの一般的な解決方法が存在しています。しかし、最新のアルゴリズムでもツリーの要素数を n として O(n3) ほどの計算量があります。
単純にツリーの変換を行おうとすると、現実的でない時間がかかってしまう。そこで、Reactは次の仮説を立てた上で、計算量を現実的なものとするアルゴリズムを作成している。このアルゴリズムの要となるのが、仮想DOM。
1.異なる型の 2 つの要素は異なるツリーを生成する。
2.開発者は key プロパティを与えることで、異なるレンダー間でどの子要素が変化しない可能性があるのかについてヒントを出すことができる。
Reactは変更を検知した際に、「変更前の仮想DOM」と「変更後の仮想DOM」の差分を実DOMに反映することで、効率的にUIの変更を反映してくれる。
このようにすることで、今までjQueryで命令的に一部の要素を変更していた実装を、宣言的に実装することが可能となる。
ただし、先述した仮説を無視するような実装を行なってしまった場合、パフォーマンスは悪化してしまう。
特に、keyは子要素の変更を効率的に行う大事な属性なので、各子要素を判別できるような一意の値(例えばidなど)を使うべき。
<ul>
<li key={Math.random()}>BAD</li>
<li key={2}>OK</li>
<li key={3}>OK</li>
</ul>
おわりに
これに気づくの遅過ぎた...😇
よく「仮想DOMのメリットは速いこと!」みたいな記事を見かけてたので、それをすっかり鵜呑みにしちゃってた感があります。
パフォーマンスもメリットの一つですが、それを踏まえた上で公式ドキュメントを読み込んで、情報を精査しないとですね。(と言いつつ、理解できてるか微妙)
宣言的UIはReactやVueだけじゃなく、最近だとSolidJSなんかもそうですよね。
SolidJS、仮想DOM無しで宣言的にコードを書くことを実現してるので、その辺りもちゃんと調査しておきたいですね。
とはいえ、従来のjQueryを混ぜた命令的でレガシーなフロント事情を、宣言的にコードを書くことを可能にして解決したReactやVueの功績は大きく感じました。