序
ここ2, 3年でSignalsという言葉は急激に広がった. しかし,この言葉はしばしば二つの異なるものを同時に指している.
ひとつは,Vueのref()やSolidのcreateSignal()のような,ランタイムで値と依存関係を保持するリアクティブ・プリミティブ という意味でのSignalsだ.
もうひとつは,SvelteやSolid,そしてVue Vaporのように,どのDOMがどの依存にぶら下がるかというtrackerまで含めて更新系を語る文脈 でのSignalsである.
私はこの二つを意図的に分けて考えた方がよいと思っている. 前者は「値のリアクティビティ」であり,後者は「レンダラのリアクティビティ」だ.
そしてこの区別を曖昧にすると,Signals = VDOMの終わり のような雑な結論に流れやすい.
しかし実際には,TC39 Signals proposal自身がFAQの中で,SignalsはVDOMにも,native DOMにも,両者の組み合わせにも載せられると明言している.
つまり,Signalsは 描画方式そのものではない.
この話をするためには少し歴史に戻る必要がある.
Knockout.jsとObservableの時代
Signalsの流行を見るたびに,私はKnockout.jsを思い出す.
Knockoutはかなり早い時期からobservableとcomputedを持ち,依存関係を自動追跡していた.
しかもKnockoutのドキュメントは,宣言的binding自体がcomputed observablesとして実装されている と説明している.
これは重要だ. 今日我々が「Signals的」と呼んでいるもののかなりの部分は,実はかなり昔からある. 値をobservableに包み,依存を追跡し,変更時にUIの一部を更新するという構図は,新しいアイデアではない.
ただし,その後しばらく標準化の文脈で前に出てきたのはSignalではなくObservableだった. TC39のObservable proposalは,DOM eventやtimerやsocketのようなpush-basedな複数値のstreamを標準化しようとしていた. 現在はWICG Observable proposal側で継続しており,そこでもhistoryとして2015年のTC39提案,2017年のWHATWG DOM issue,2019年の再活性化が整理されている.
ここで一度整理しておきたい.
Observableが主に扱うのは,時間方向に流れるevent streamだ.
Signalsが主に扱うのは,「いまの値」とその依存グラフだ.
両者は近いが同じではない. Observableはtemporalであり,Signalはcurrent-value orientedだ. だから,TC39におけるObservableの歴史と,現在のSignals proposalは連続してはいるが,そのまま同一視はできない.
しかもSignals proposalは,framework authorsの協調の上で,developer-facing surface APIよりも,underlying signal graphのcore semanticsを揃える ことに重心を置いている. ここでもう既に,「Signalsとはend-user APIではなく,frameworkの下に潜る基盤である」という意識が見えている.
SignalsとSignals.
ここからが本題だ.
私が言いたいのは,Signalsという語には少なくとも二つのスコープがある,ということだ.
1.ランタイム・プリミティブとしてのSignals
これは最も狭い意味でのSignalsだ.
Vueのreactivity docsは,Vue 3ではreactive() がProxyを使い,ref() がgetter / setterを使ってtrack() / trigger() を実現していると説明している.
また同じページでVueは,自身のreactivity systemがprimarily runtime-basedであると明言している.
SolidのcreateSignal()も同様に,getterがreactive context内で依存を追跡し,setterがdependent computationsを通知するprimitiveとして説明されている.
この層でのSignalsは,かなり素朴に次のようなものだ:
signal cell -> track reads -> trigger writes -> rerun effects
ここで主役なのは 値 である. どの値が読まれたか,どのeffectがそれに依存しているか,という依存グラフがランタイムに構築される.
Vueのrefも,SolidのcreateSignalも,この文脈でまず理解できる.
2. DOM trackerまで含めたSignals
しかし,もうひとつ別の文脈がある.
Svelteは,compilerによってbrowserでの仕事を最小化するframeworkであると自身を説明している. TC39 Signals proposalのFAQも,Svelte 5がrunesを内部のsignals libraryにtransformする例として挙げている. SolidのREADMEは,templateをreal DOM nodesにcompileし,whole buttonをrerenderするのではなく,必要な場所だけをupdateするコードを生成することを示している. またSolid Docsのfine-grained reactivityは,Reactがcomponent全体を再実行しがちなのに対して,Solidはtarget attributeを更新すると説明している.
このときSignalsは,もはや単なる値containerではない. どのsignalがどのDOM text node / attribute / bindingを更新するか,というDOM側のtrackerまで含めた更新文脈になる.
signal graph -> compiler-known DOM sinks -> exact DOM mutation
ここではrendererの仕事の一部がdependency graphに吸い込まれている. 再実行の単位はcomponent全体ではなく,もっと細いDOM sinkに近づく.
この意味でのSignalsは,値のprimitiveというより 描画アーキテクチャの名前 である.
ここでは同じSignalsという語を重ねている.
Retained UI
ではVDOMやReact Fiberはどこに位置づくのか.
私はこれをRetained UIと呼びたい. つまり,一度desired UIをメモリ内のnode / frame / treeとして持ち,それをもとに更新を管理するランタイムである.
ReactのRender and Commitは,renderでcomponentを呼び,commitでDOMに反映すると説明している. 再renderの際にはchanged propsを計算し,commitではminimal necessary operationsを適用する.
さらにReact Fiber architectureでは,
render時にappを記述するtreeがmemoryに生成されること
そのtreeをdiffしてDOM operationsを計算すること
Fiberはunit of workであり,virtual stack frameのようなものだということ
が説明されている.
これは非常に重要だ. Reactにおいて主役なのは,signal dependency graphではなくin-memory tree / fiber graphの方だ. state updateはそのtreeを再計算するための引き金であり,最終的なDOM updateの主体はreconciler側にある.
つまりReactでは,
state update -> rerun components -> new in-memory tree -> reconcile -> commit DOM
である.
ここでReactivityは存在しないわけではない. しかしそれはVueやSolidのようにfirst-classなdependency graphとして前景化しているわけではない. Reactの場合,reactivityは主に どのcomponent subtreeを再評価するか というinvalidationの問題として現れ,実際の更新はRetained UIが引き受ける.
Vueはその中間にいる
面白いのは,Vueが昔からこの二層を両方持っていることだ.
templateをrender functionにcompileし
mountをreactive effectとして実行し
dependency change時にeffectをrerunして新しいVDOM treeを作り
patch / reconciliationでDOMを更新する
と説明している.
さらにVueは,同じページでpatch flagsやtree flatteningを用いたcompiler-informed virtual DOMを説明している. つまりVueは昔から,
下層には
ref/reactive/ effectというリアクティビティ上層にはcompiler hints付きVDOM runtime
を重ねていた.
これはReactとSolidの中間というより,SignalsとRetained UIを両方持つ構造 と言った方がよい.
だからVueを見ているとわかる. SignalsはVDOMの否定ではない. SignalsはVDOMの下にも置けるし,上にも別のrendererを置ける. 実際TC39 Signals proposalのFAQも,SignalsはVDOMにもnative DOMにもcombinationにも載ると言っている.
Vue Vaporの未来
この観点からVue Vaporを見ると,話はかなり明確になる.
2025年12月23日のVue 3.6.0-beta.1 release noteは,Vapor Modeを
Vue SFCのnew compilation mode
baseline bundle sizeとperformance改善を目指すもの
100% opt-in
feature-completeだがstill unstable
と説明している.
しかも同じrelease noteには,かなり重要なことが書いてある.
Vapor-only modeではSuspenseを直接は持たないが,VDOM Suspenseの中でVapor componentをrenderできる
createApp側にvaporInteropPluginを入れると,VDOM appの中でVapor componentを使える 3.逆にVapor app側でもinterop pluginを入れればVDOM componentを使える 4.ただしmixed nestingにはrough edgesがあり,distinct regionsを推奨する
これはかなり示唆的だ. 少なくとも近未来のVue Vaporは,「VDOMを完全に捨てて全面移行する新しいVue」ではない. むしろ 高性能subsetとしてのVaporを,既存Vue runtimeとinteropさせながら段階的に広げる構想に見える.
SSR / Hydration
Template Ref Interop
Suspense support
Vue Router
Pinia
Nuxt.js
DevTools Integration
Vue Test Utils
といった項目が並んでいる.
ここから読み取れるのは単純だ. Vaporの本質的な課題は,単に「速いDOM更新ができるか」ではない. Vue ecosystem全体とどう噛み合うか が課題なのだ.
つまりVue Vaporの未来は,私はこう見る.
VDOMがlegacy residueとして残るのではない
Vaporがperf-sensitive pathを担う高性能subsetになる
VDOM runtimeはinterop / ecosystem / library compatibilityの母体として当面は重要なままである
そして両者の境界が,より明示的に設計されていく
これはむしろVueらしい. Vueは昔からprogressive / incrementally adoptableであることを重視してきた. Vaporが将来成功するとしても,その成功の仕方は「全部置き換える」より,「適切な境界を持って共存する」に近いはずだ.
なぜこれはReactとの比較で重要なのか
ReactではReactivityよりもRetained UIの方がコアであり,Fiberはそのscheduling / reconciliation / prioritizationを担う. 一方でVapor / Svelte / Solid Compilerの方向は,依存追跡とDOM sink trackingを近づけることで,保持されたtreeへの依存を薄くしようとする.
ここで比較すべきなのは「どちらが上か」ではない. 比較すべきなのは,どのレイヤが更新責任を持つか だ.
React / classic VDOM: in-memory node runtimeが更新責任を持つ
runtime signals: signal graphがinvalidationを持つが,rendererは別にいる
compiler + DOM tracker signals: signal graphがrendererの責務の一部まで持ち始める
この三つは別物だ.
そしてVueは今,その全部を一つの体系の中に保持したまま前進しようとしている.
refは残り,VDOM modeも残り,その上でVaporが増える.
私はこれをかなり健全だと思っている.
Signalsは魔法の銀の弾丸ではない. またVDOMは単なる過去の遺物でもない. Retained UIは,component composition,dynamic rendering,ecosystem interop,debuggability,toolingとの整合において依然として強い. 一方でcompiler-guidedなSignals.は,hot pathの細粒度更新とbaseline sizeの面で強い.
問題は常に「どちらを捨てるか」ではなく,「どこに境界を引くか」なのだ.
これは私の個人的な考察であり,Vue Team / React Team / TC39の公式見解ではありません.