More than 1 year has passed since last update.

TL;DR

VirtualDOMを狭義と広義に分けて定義すると、

  • 狭義のVirtualDOMとは、実際のDOMを表象する構造体
  • 広義のVirtualDOMとは、狭義のVirtualDOMに加えてdiff, patchメカニズム

とすることができ、React.js(及びVirtualDOM実装と呼ばれるMatt-Esch/virtual-domsegmentio/deku)がシンプルなのは後者を実装しているからである。

Ractive.jsとReact.jsはどう違うか、という話

なるほどRactive.jsのリポジトリにはvirtualdomという名のディレクトリがある。このvirtualdomディレクトリの中では、構造体をDOMへと投影するような実装がされている。このことを根拠に、Ractive.jsはVirtualDOMと呼ぶこともできる。

Ractive.jsにおける実際のDOM変更処理はViewModel(端的に説明するとRactiveインスタンス生成時にdataをラップしたもの)のうち、変更された部分をマークしておいて、紐付いたDOMパーツをアップデートするというアプローチが採用されている。これはReact.jsの「実際のDOMに対応する構造体をstate変更ごとに生成し、変更前後の構造体を比較することでDOM変更の回数を小さくする」アプローチとは全く異なる。

Ractive.jsはVue.jsのようにドラスティックな変更をViewModelに対して施さないので、手触りとしてはより直感的だった(それでもdataに置かれたArrayのプロトタイプチェーンがいじられたりする)。Ractiveインスタンスを実DOMの世界にマウントする際、内部的にはテンプレートのどの部分がViewModelのどのキーとバインドされるかを計算しており、そういう意味でもReact.jsより他の2way bindingに近い。

Ractive.jsのこの実装が抱える最大の問題は、ViewModelにテンプレートの内容(テンプレートのうちどんなパーツがViewModel上のどこと関係しているかという情報)を知らせるために、コードが複雑になりがちということである。

その一方で、ある値を用いて実DOMを表象する構造体を計算するのは単純な関数として実装できる。これがReact.createClassにおけるrenderの意味するところである。実DOMの変更が必要かは、関数の結果として得られる構造体をstateの変更前後について比較する段階で判断される。つまり、state (data) が出力されるDOMや具体的なテンプレートについて関知することは不要となる。

ひいては、React.jsやその他VirtualDOM実装はstate (data) のどこがどう変更されたかについては鈍感でも構わないということになる。単に変更されたことが通知され、DOMを表象する構造体を計算する関数さえ呼び出されればよくなる。

ということで、React.jsに代表されるVirtualDOM実装がシンプル(特にsegmentio/dekuはコードも少ないのですぐ読める)なのは、構造体の比較によりDOMのどの部分を変更すればいいか判断する仕組み(diff, patchのメカニズム)があるからであって、実DOMを表象する構造体の存在が実装をシンプルにするわけではない(なお、ここで指すシンプルは実装のシンプルさであり、ライブラリ利用者がシンプルに使えることを意味してはないない)。

なので、混乱を避けるために自分の中では以下のように定義している。

  • 狭義のVirtualDOMとは、実際のDOMを表象する構造体
  • 広義のVirtualDOMとは、狭義のVirtualDOMに加えてdiff, patchメカニズム

で、Ractive.jsどうなの

前述したとおり、VirtualDOM実装は内部的にシンプルな実装ではあるが、ライブラリ利用者がシンプルに使えるわけではない。renderするまでの道のりにしても、JavaScriptの知識がないとゴールまでたどり着けない。ゴールへたどり着く早さはやはりRactive.js含む2way bindingに一日の長がある。

参考資料

Virtual DOM and diffing algorithm