Svelteの特徴
Svelteはコンパイラ
ところで、なぜそのようなことが可能なのでしょうか。
そもそも、Reactがステート変数の定義・更新に特別な関数を必要とするのはなぜでしょうか。
Reactはフロントエンドのコードのすべてを管理しているわけではないので、単にJavaScriptの処理系がcount += 1
のような代入処理を実行しただけでは、そのことを知ることができません。しかし、それを知ることができなければ、ユーザに表示されたcount
の値は、古い表示のままとなります。先ほどの例でいえば、ユーザがボタンを押しても「クリックした回数」の右側にある数字が増えていかないことになります。Reactを使う開発者は、Reactが提供するこれらの関数を呼び出すことで、Reactに「状態変数が変わったから、必要に応じてレンダリング内容を更新してね」と伝えていることになります。
ところで、Svelteで開発したフロントエンドも、Reactと同様にブラウザ上で実行されるので、単に処理系がcount + =1
のような代入処理を実行しただけでは、そのことを知ることができないはずです。にも関わらず、先ほどのSvelteの例では、単にcount += 1
と書いただけで、表示内容はきちんと更新されていました。Svelteには何か魔法でもあるのでしょうか?
答えはNoでありYesです。Svelteは神秘的な力で動いてはいませんが、コンパイラ技術を魔法と呼ぶのであれば、その通りだとも言えます。
Svelteで書かれたコンポーネントは、そのままブラウザに読み込まれるのではなく、Svelteによってランタイムなしで実行可能な形に変換されて読み込まれます。このような処理を「コンパイル」といい、Svelteに詳しい人たちが特徴を語るとき、「Svelteはコンパイラだ」と言ったりします。
コンパイルに際して行われる変換の一つとして、Svelteは代入処理を行うコードをcount += 1
から$$invalidate(0, count += 1);
といった形に変更します。つまり、Reactで見たのと同じように、代入処理が実行されたときには必ず特別な関数を呼び出して知らせているのです。
重要な違いは、その仕事を人間ではなくSvelteがやってくれるということです。当然、書き忘れて代入処理だけ書いてしまう、などという人間らしいミスは起きることがありません。
仮想DOMを使わない
Svelteがコンパイラであることには、もうひとつの大きな狙いがあります。それは、コンポーネントのコードやDOM構造を解析するタイミングを、実行するときから開発するときに移すことです。
コンポーネントの実現方法はフレームワークによって様々な戦略があり得る一方で、Web開発においては最終的な文書構造はHTMLの一部、DOM構造として表現する必要があります。フレームワーク内で完結するコンポーネントの内部状態を更新することに比べると、DOMの更新はコストのかかる処理です。一つDOM要素を追加するだけで、レイアウトの再構築やCSSOMの更新などさまざまな処理を自動で実行します。
先ほど見た単純な例だけでもdiv
、p
、button
の3つの要素がありました。しかし、実際にcount
ステートの変化によって直接影響を受けるのはp
要素だけです。それもDOM要素全体を完全に作り替える必要はなく、単にテキストを更新すれば良いだけです。DOM構造上のどの箇所が変化したのかを検出することができれば、コンポーネントのステート変化を画面に適用するコストを大幅に削減することができそうです。これを実現しているのが、Reactなどのフレームワークで採用されている「仮想DOM(Virtual DOM:VDOM)」という手法です。
仮想DOMでは、ブラウザが管理する実際のDOMとは別に、div
> p
> button
といったデータ構造をJavaScript上で記録しておきます。これが「仮想DOM」と呼ばれる理由です。コンポーネントの状態が変化するたびに、新しい仮想DOMを生成しますが、これは実際のDOM 構造の更新に比べるとかなりコストの低い処理です
[Before] [After] element div element div children children element p element p text: クリックした回数: 3 text: クリックした回数:4 element button element button text: クリックしてください text: クリックしてください
新しい仮想DOMと古い仮想DOMを比べると、大半の部分は変化しておらず、単に文字列が変化しただけだということがわかります。それがわかれば、コストのかかるDOM要素の生成は差し替えなどはする必要がなく、次のように直接テキストを更新しれば良いと判断できます。
if (changed.name) { text.data = name; }
当然のことですが、仮想DOMは、毎回DOM構造を作り直したり、何か変化するたびに要素ごと作成して差し替えたりすることに比べれば高速ですが、それでも、前述のような「DOM構造上のテキスト部分だけ変更する」処理に比べるとコストのかかる処理です。もしここだけを実行できれば、それに越したことはないはずです。
Svelteがコンパイラという戦略を採ったのはまさにこの理由からです。Reactがコンポーネントのコードを評価するのは、ユーザのブラウザにロードされて実行されたときが初めてです。一方、Svelteではユーザに配信する前のビルドをする過程で、Svelteによってコンポーネントのコードがコンパイルされて、この際にDOM構造がどのように変化するかを掌握してしまいます。その結果「このタイミングではテキストだけが変化するな」「このイベントが走ると、要素が追加されたり削除されたりするな」ということを、Svelteが把握することができ、それぞれに応じた必要最小限のコードを生成することができます。
実際に実行されるのは、jQueryやVanlla JS(普通のJavaScript)で書いていたようなDOM要素の属性の更新処理がほとんどとなります。ここでも重要になってくるのは、Svelteではこうした処理を書くのが人間ではなく、コンパイラだということです。人間は、Reactで書くのと同様な抽象度のコードを書けばよいのです。そもそもReactが解決したのは、複雑化したフロントエンドのDOM構造を適切に把握することは人間には難しい、という課題でした。Svelteは、同じ課題に対する別のアプローチによる解だと言えます。