Web●●●○○

リアクティビティ

りあくてぃびてぃ

データの変化を自動で検知してUIへ伝播させる仕組み。宣言的UIを支えるもう一つの柱。

概要

リアクティビティ(reactivity)は、データが変わったとき、そのデータに依存しているものが自動で追従して更新される仕組みです。もっとも身近な例はスプレッドシートです。セルB1に「=A1*2」と書いておけば、A1を書き換えた瞬間にB1が勝手に再計算されます。「A1が変わったらB1も更新する」という手続きはどこにも書いていないのに、依存関係の宣言だけで伝播が起きる — これがリアクティブな計算モデルです。

フロントエンドの文脈では、状態(データ)と画面(DOM)の間にこの性質を持ち込みます。count という値を表示に使っていれば、count を書き換えるだけで表示が追従する。Vue のリアクティブオブジェクト、SolidJS や Preact の signals、Svelte の runes などがこの系譜で、仮想DOMと並ぶ「宣言的UIを支えるもう一つの柱」です。

なぜ生まれたか

リアクティビティ以前、データと画面の同期は手作業でした。値を書き換えたら、その値を表示しているすべての箇所を開発者が思い出して、JavaScript で1つずつDOMを更新する。表示箇所が増えるたびに更新コードも増え、1か所でも忘れれば画面とデータが食い違います。オブザーバパターン(変更を購読者に通知する設計)を手で組むこともできますが、購読の登録・解除や通知の配線を全部自前で管理するのは、それ自体が大きな負担でした。

この「変更の伝播を人間が配線する」仕事を、システムに肩代わりさせようというのがリアクティビティの発想です。ルーツはスプレッドシートや、学術的にはFRP(関数型リアクティブプログラミング)にあり、フロントエンドでは Knockout.js(2010年)のオブザーバブルや AngularJS のデータバインディングを経て、Vue が「普通のオブジェクトを書き換えるだけで画面が追従する」体験として大衆化しました。近年は signals という形で各フレームワークに再輸入され、標準化の議論も進んでいます。

詳細

依存追跡 — 「読まれたこと」を記録する

リアクティビティの核心は依存追跡(dependency tracking)です。仕組みは意外と素朴で、(1)状態の読み取りを検知できるようにしておき、(2)計算や描画の実行中に「どの状態が読まれたか」を記録し、(3)その状態が書き換えられたら、記録しておいた計算だけを再実行します。つまり「A1が変わったらB1を更新」という配線を、B1の式を最初に評価したときに自動で張ってしまうのです。

読み書きを検知する実装方式は世代ごとに進化してきました。Vue 2 は Object.defineProperty で各プロパティを getter/setter に置き換える方式で、後からのプロパティ追加や配列の添字代入を検知できない弱点がありました。Vue 3 や MobX は JavaScript の Proxy(オブジェクトへのあらゆる操作に割り込める言語機能)でこれを解決しています。SolidJS や Preact signals、Angular の signals は、値を count() / count.value のような明示的な入れ物(シグナル)に包む方式で、何がリアクティブかをコード上で明確にします。Svelte はコンパイラが代入文を検出して更新コードを織り込む、第三の路線です。

仮想DOM方式との対比 — 再計算か、ピンポイント更新か

宣言的UI(状態を書き換えれば画面が追従する開発体験)を実現する路線は大きく2つあります。仮想DOM方式は「状態が変わったらコンポーネントを再実行して木を作り直し、差分を探す」— 変更箇所を毎回計算で突き止める方式です。リアクティブ方式は「どの状態がどこで使われているかを最初から知っている」ため、差分計算をせず依存グラフをたどって該当箇所だけを直接更新します。

仮想DOM方式状態が変わるコンポーネントを再実行し仮想木を丸ごと再生成前回の木と差分検出差分だけ実DOMを更新更新箇所を毎回「計算して見つける」リアクティブ方式シグナルが変わる記録済みの依存グラフをたどる依存するDOMノードだけ更新更新箇所を最初から「知っている」
同じ宣言的UIでも更新戦略が異なる — 作り直して比べるか、依存をたどって直撃するか

どちらが優れているという話ではなく、設計思想の違いです。仮想DOM方式はコンポーネント単位の粗い粒度で動くため、モデルが単純で「毎回全部書き直す」感覚を貫けます。リアクティブ方式は細粒度(fine-grained)で無駄がない代わりに、依存グラフという状態機械を裏に抱えます。近年は React 以外の主要フレームワークが signals 系へ収斂しつつあり、SPA の性能最適化の文脈で両者の対比が語られることが増えました。

派生値と副作用、そして落とし穴

実用的なリアクティブシステムは、生の状態(signal / ref)に加えて、他の状態から計算される派生値(computed / memo。結果はキャッシュされ、依存が変わったときだけ再計算)と、DOM更新や通信のような副作用を実行する仕掛け(effect / watch)の三点セットで構成されます。依存が菱形につながっても中間値の不整合(グリッチ)を見せないよう、更新順序をトポロジカルに制御するのが各実装の腕の見せどころです。

落とし穴も依存追跡ゆえのものです。追跡できない書き換え(Proxy を通さない生オブジェクトへの代入など)は画面に反映されず、逆に effect の中で自分の依存先を書き換えると更新が循環します。また「魔法のように動く」ことの裏返しで、どこで何が購読されているかが追いにくくなる問題は、状態管理の設計と合わせて考える必要があります。魔法の正体は「読み取りの記録と通知の配線」である — これを押さえておけば、挙動が不思議に見えたときに立ち返る場所ができます。