Svelte:新しいフロントエンドフレームワークの登場
最初にSvelteを見たとき、「ああ、また新しいフロントエンドフレームワークか」と思ってあまり気にしなかった。しかし、最近では多くのブログ記事やウェブサイトで紹介されていることを見て、私の好奇心が引かれるようになった。
公式ウェブサイトのチュートリアルとドキュメントを見て初めて、これは通常の「フロントエンドフレームワーク」とは異なるアプローチで、文法レベルでコンパイラと組み合わせてシンプルで効率的なコードを書くものだと気づいた。
次のフレーズに私の興味を引かれました:
Svelteはコードを小さなフレームワークのないバニラJSにコンパイルします - あなたのアプリは高速に起動し、高速であるままです。
実際に一度書いてみたところ、Reactに慣れている私はいくつかの方法について考える必要がありましたが、全体的に非常に好きで、他の(React、Vueなどの)フレームワークとはまったく異なるコンセプトです。
私は皆さんに、Svelteの作者がYGLFでの講演「Rethinking reactivity」を見て、フロントエンドフレームワークの本質と私たちが何を実現できるかを再考してみることをお勧めします。以下は、この講演についての私のメモと考察です。
リアクティビティとは何か?
関数型リアクティブプログラミングの本質は、値の動的な振る舞いを宣言時に完全に指定することです - Heinrich Apfelmus
私たちがフロントエンドフレームワークを使用する際、通常2つのことを考慮します:
- 仮想DOM:ページのレンダリングパフォーマンスの確保
- リアクティビティ:値の変更を追跡する
フロントエンドフレームワークの最も重要な部分は、データフローとデータの変更のトラッキングです。
仮想DOMの使用理由
主な理由は、データの更新がページ全体の再レンダリングに影響すると、パフォーマンスに大きな影響があるためです。そのため、仮想DOMは通常、必要な変更のみを再レンダリングするための効率的なdiffアルゴリズムを実装しています。
しかし、問題があります。安定したdiffアルゴリズムと更新メカニズムを実装するには、非常に多くの作業が必要で、パフォーマンスの制約も考慮する必要があります。ツリーの深さが深くなると、パフォーマンスのボトルネックが発生する可能性があるためです。
リアクティビティ
Reactは、データの変更を追跡するためにsetState
やuseState
を使用します。一方、VueはProxyの方式を採用し、開発者が値にアクセスする際にリアクティビティメカニズムをトリガーし、データの変更を追跡します。
Reactでは、useState
またはthis.setState
を使用して値の変更を検知し、余分な更新を行わないようにするメカニズムがあります。以下のコードを見てみましょう:
const Counter = () => {
const [counter, setCounter] = useState(0);
const handleClick = () => {
setCounter(c => c + 1)
}
return <div onClick={handleClick}>{counter}</div>
}
コンポーネントが更新されるたびに、useState
とhandleClick
関数が再評価されます。React内部ではこれらの状態の変化を保存し、適切な更新を行います。
この問題を解決するために、useMemo
や他の最適化手段が導入されました:
shouldComponentUpdate
React.PureComponent
useMemo
useCallback
これらのメカニズムの実装には多くの労力がかかり、画面の再レンダリング時に全体を再描画しないようにするためのものです。これにより、バグの少ないコードが作成され、面倒な作業ではなく創造的な作業に時間を費やすことができます。しかし、これらのメカニズムにより、reactやreact-domのバンドルサイズが非常に大きくなります。
実際、作者自身が公式ブログで「Virtual DOMは純粋なオーバーヘッド」という記事を公開し、Virtual DOMによってもたらされる利点と犠牲を説明しています。記事の結論を引用すると:
重要なのは、仮想DOMは「機能」ではなく、「目的」です。それは、状態遷移について考えずにアプリを構築し、一般的に十分なパフォーマンスを持つことを可能にするための手段です。これにより、バグの少ないコードが作成され、退屈な作業ではなく創造的な作業に時間を費やすことができます。
しかし、仮想DOMを使用せずに同様のプログラミングモデルを実現することもできます - それがSvelteの役割です。
また、記事中には興味深いツイートもあります:
Why is the conclusion that there's something wrong with the framework, instead of something being wrong with the platform? If the DOM provided a way to efficiently supply large trees created functionally, without React having to do a diff, we'd use that API.
— Vim Diesel ⚛️🆁 (@jordwalke) July 8, 2018
仮想DOMのメカニズムは、フレームワークが状態管理について心配しなくても済むようにするために存在しますが、なぜこのメカニズムをプラットフォーム自体が提供しないのでしょうか?これは興味深い視点ですが、実際に実装するためにはどれほどの労力が必要か、またブラウザによる互換性や規格に対する問題が発生する可能性があるため、フレームワーク自体よりも進化の速度が遅いかもしれません。
バンドルサイズが大きいわけではありませんが、製品のコードと組み合わせると、このバンドルサイズは驚くほど大きくなります。
以上の考えをまとめると、現代のフロントエンドフレームワークの欠点は次のとおりです:
- ランタイムのリアクティビティ+仮想DOMのdiffメカニズム
- 大きなバンドルサイズ
バンドルサイズやパフォーマンスについて気にする必要があるかどうかは人それぞれかもしれませんが、いつかはハードウェアの性能やネットワークの速度が追いつくでしょう。そのときには差があまり明確ではなくなり、コード分割やダイナミックインポートなどの手法を使用して初期のバンドルサイズを効果的に減らすこともできます。
また、強力なトランスパイラであるBabelを使用することで、JavaScriptにBabelのサポートを加えることで、ランタイムで行う必要のある複雑なタスクをできるだけ減らすことができます。また、WebAssemblyやArrayBufferなどのlow-level APIの進歩により、Webの発展は新たな局面を迎えるかもしれません。この時代では、フロントエンドだけでなく、シンプルなコンパイラを作る方法も学ばなければなりません。