ここ2, 3年でSignalsという言葉は急激に広がった. しかし,この言葉はしばしば二つの異なるものを同時に指している.

ひとつは,Vueのref()SolidのcreateSignal()のような,ランタイムで値と依存関係を保持するリアクティブ・プリミティブ という意味でのSignalsだ. もうひとつは,SvelteSolid,そしてVue Vaporのように,どのDOMがどの依存にぶら下がるかというtrackerまで含めて更新系を語る文脈 でのSignalsである.

私はこの二つを意図的に分けて考えた方がよいと思っている. 前者は「値のリアクティビティ」であり,後者は「レンダラのリアクティビティ」だ.

そしてこの区別を曖昧にすると,Signals = VDOMの終わり のような雑な結論に流れやすい. しかし実際には,TC39 Signals proposal自身がFAQの中で,SignalsはVDOMにも,native DOMにも,両者の組み合わせにも載せられると明言している. つまり,Signalsは 描画方式そのものではない.

この話をするためには少し歴史に戻る必要がある.

Knockout.jsとObservableの時代

Signalsの流行を見るたびに,私はKnockout.jsを思い出す. Knockoutはかなり早い時期からobservablecomputedを持ち,依存関係を自動追跡していた. しかも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が昔からこの二層を両方持っていることだ.

VueのRendering Mechanismは,

  1. templateをrender functionにcompileし

  2. mountをreactive effectとして実行し

  3. dependency change時にeffectをrerunして新しいVDOM treeを作り

  4. 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には,かなり重要なことが書いてある.

  1. Vapor-only modeではSuspenseを直接は持たないが,VDOM Suspenseの中でVapor componentをrenderできる

  2. 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させながら段階的に広げる構想に見える.

Vapor Roadmapでも,

  • 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の公式見解ではありません.