Vapor モードとは何ですか#
Vapor モードは、中国語で蒸気モードと直訳されます。
これは、Vapor の GitHub リポジトリからの説明の一部です。
Vapor モードを開発するために、Vue チームは vue3 のメインブランチから新しいリポジトリ「core-vapor」をフォークしました。目標は、Vue の仮想 DOM を使用しないレンダリングモードを実現することです。
ここで、Vue の開発プロセスに詳しい人は、**「Vue1.0」は仮想 DOM を使用しないバージョンではないのかと思うかもしれません。なぜこれほど多くの年月が経過しても、逆戻りしているのでしょうか?ここで、Vue の各メジャーバージョンの「アーキテクチャの進化の歴史」** を整理する必要があります。
Vue 1.0:量子もつれの細粒度バインディング#
これは、Vue が初めて形を成したマイルストーンであり、この時点では完全な反応型システムはまだありませんでした。この時期、私たちが最も話題にしていたのは、データプロキシ + 依存関係の収集に基づく反応型の実装方法と、ウォッチャーとデプの 2 つの兄弟の実装の詳細です。
理解していない人は、この「レンダリングフローチャート」を見て、Vue1.0 のバージョンについての理解を早めることができます。
**SFC(単一ファイルコンポーネント)が存在しない前に、Vue は実際の「ランタイム」** フレームワークと言えます。上の図は、**new Vue ({...})** を実行した後に発生する一連のプロセスを簡潔に示しています。
-
データプロキシによるデータの監視を実現するために、definePropertyを使用して、データへのすべてのgetおよびset操作をプロキシし、これは「getter/setter」化とも呼ばれます。
-
プロパティの「getter」の主な目的は、依存関係の収集です。ここでの「依存関係」とは、そのプロパティにアクセスするすべてのロジックを指します。これは「計算プロパティ」、リスナー、テンプレート内のディレクティブや補間式のいずれかかもしれません。実装のレベルでは、これらの異なる依存関係は統一された概念であるウォッチャーとして抽象化され、各プロパティのデプインスタンスに格納されます。
-
プロパティの「setter」は、データが変更された後、デプに収集されたウォッチャーにビューの更新を通知します。
全体のプロセスでは、Vue の依存関係の収集の **「粒度」は非常に細かいです。「反応型」データにアクセスする場所はすべて、「依存関係」** として収集されます。
上位レベルで言えば、「データ」とそれに対応する「UI」の間にはバインディング関係が形成されます。これにより、Vue は「ビューの更新」時に非常に高い効率を持つことができます。このバインディング関係は、量子もつれのように、**「データ」が変化すると同時に「UI」** が通知を受け取り、更新を行います。
しかし、すべてのものには 2 つの側面があります。細かすぎる依存関係の収集は利点でもあり短所でもあります。プロジェクトの規模が大きくなるにつれて、実行時にはますます多くのウォッチャーとデプが生成され、多くのメモリを使用し、それによってページのパフォーマンスが低下します。
小規模なプロジェクトの **「パフォーマンス」** が優れているだけのソリューションでは、明らかに「最適な解決策」ではありません。
Vue 2.0:依存関係の収集の粒度を調整し、仮想 DOM を導入#
大規模なプロジェクトでより良いパフォーマンスを実現するために、Vue2.0 では大幅な調整が行われました。以下のフローチャートから、Vue のアーキテクチャの変化が直感的にわかります。
- 1.0バージョンと比較して、コンポーネントの概念が導入されました。
- 依存関係の収集の粒度が「コンポーネントレベル」に調整され、つまり、1 つのコンポーネントは 1 つのウォッチャーです。
- 仮想 DOMが導入され、非常に重要な役割を果たすようになりました。
全体的に見ると、「反応型」データはもはやコンポーネント内部の「依存関係」に関与せず、データが変更された場合には単にコンポーネントに通知され、コンポーネントは差分アルゴリズムを使用して「仮想 DOM」の変更部分を見つけ出し、これを「実際の DOM」に更新します。これは本質的には時間と空間のトレードオフであり、**「ランタイム」の「更新効率」を適度に低下させることで、より少ない「メモリオーバーヘッド」** を得るためのものです。
同時期に、Vue はSFC(.vue ファイル)も追加しました。なぜなら、.vueファイルはフレームワークが提供する"マジック"
であり、直接ブラウザに実行させることはできないため、"マジック"
を破壊するツールが必要です。これにより、.vueファイルを **.jsに変換する必要があります。このツールの核心はコンパイラ(compiler)であり、"マジック"
を破壊するプロセスはコンパイル(compile)と呼ばれます。つまり、コンパイル後、.vueファイルに書かれたv-if, v-for, インターポレーション
などの機能は、通常のJavaScriptロジックに変換されます。この段階で、templateはrender** 関数に変換されます。
1.0 バージョンでもcompileプロセスが存在していましたが、その役割は大きく変わりました。
compile | 1.0 | 2.0 |
---|---|---|
フェーズ | ランタイム | コンパイル時 |
役割 | テンプレートのディレクティブ、インターポレーションなどを解析して対応するロジックを実行する | テンプレートを抽象構文木(AST)に解析し、レンダリング関数を生成する |
簡単に比較すると、実行フェーズのコンパイルは完全に異なるものになりましたが、テンプレートの解析は主要なトピックです。
実際には、ここまでくると、Vue はアーキテクチャ的に最適化の限界に達しているように感じるかもしれません。しかし、Vue 3.0 の進化を見た後で判断しましょう。
Vue 3.0:コンポジション API#
TypeScriptのフロントエンドでの普及と、主要なブラウザがサポートするようになったプロキシによるメタプログラミング能力を代表するものとして、Vue3は完全に再構築されました。
同時に、オプションAPIのロジックが分散しすぎており、開発者が高い内部結合のコードをエレガントに書くことが難しい
という痛点を解決するために、React hooksの考え方に触発され、Vue3 はコンポジション APIも導入しました。間にはミックスインなどのソリューションもありましたが、いくつかの欠点がありました。
そして、コンポジション APIモードでは、フックを使用してビジネス機能を簡単に組み合わせ、ロジックを内部結合させることができます。
新機能以外にも、細部の改善も行われました。例えば、
- プリストリング化
- 静的ノードのアップグレード
- パッチフラグ
注意深い学生はすでに気づいているかもしれませんが、これらの最適化はすべてコンパイル時の最適化であり、ランタイムの最適化ではありません。なぜなら、ランタイムでは最適化できる余地がほとんどなく、基本的には差分アルゴリズムに関連するパッチプロセスに焦点を当てています。
Vapor について再び#
Vue の進化を振り返った後、主題であるVapor モードに戻ることができます。これは、Vue がコンパイル時の最適化に関して研究を行っているものであり、またSolidJSの影響を受けています。
前述のように、Vue のアーキテクチャに基づいて、ランタイムの最適化はほぼ仮想 DOMの変更部分をより速く見つける方法に焦点を当てることしかできません。
もし、仮想 DOM のない Vue1.0 の形態に戻るとすると、細粒度バインディングは変更を見つける必要がなくなり、仮想 DOM も必要ありませんが、問題は次のとおりです。
-
依存関係はランタイムで決定されます
。システムの初期化には、コンパイルフェーズでディレクティブなどを解析し、依存関係を収集する必要があり、初回レンダリングの時間が増えます。 -
細粒度の依存関係の収集により、大量のウォッチャーが生成され、より多くのメモリオーバーヘッドが発生します。
ここで、依存関係の決定タイミングをランタイムからコンパイル時に移動し、変化する反応型データごとに実行する更新ロジックを生成することで、上記の第 1 の痛みを解消できるのではないでしょうか。これがVapor モードのアイデアです。
第 2 の痛みについては、理論的には、依存関係の収集の粒度を粗くするほど、より多くのメモリオーバーヘッドが発生する可能性があります。Vapor モードでは、どのように処理されるのかは疑問ですが、公式リリース後に確認しましょう。