フレームワークなしのリアクティビティ:今日のネイティブJSでできること
リアクティブなUI動作、つまり状態変更が自動的にDOMを更新する機能が欲しいが、シンプルなウィジェットのために40KBものフレームワークコードを配信したくない。朗報です:バニラJavaScriptによるリアクティビティは、ブラウザで長年安定しているAPIを使って完全に実現可能です。
この記事では、2025年後半時点でリアクティブUIを構築するために利用可能なネイティブツールを取り上げます:Proxyベースのリアクティブステート、pub/sub用のEventTargetとCustomEvent、そしてDOM対応リアクションのためのブラウザオブザーバーです。今日使えるもの、今後登場するもの、そしてこれらのパターンがフレームワークの内部構造とどう対応するかを学びます。
重要なポイント
- Proxyオブジェクトはプロパティの変更をインターセプトし、フレームワーク依存なしで自動的なDOM更新を可能にする
- EventTargetとCustomEventは、疎結合なコンポーネント通信のためのネイティブpub/subレイヤーを提供する
- ブラウザオブザーバー(MutationObserver、IntersectionObserver、ResizeObserver)はDOMとレイアウトのリアクティビティを処理する
- TC39 Signalsプロポーザルはリアクティビティプリミティブを標準化する可能性があるが、現在のProxy + EventTargetパターンでも同様の結果を実現できる
リアクティビティの本質的な意味
リアクティビティはシンプルなループです:状態変更がUI更新をトリガーします。フレームワークは仮想DOM、コンパイラ、またはきめ細かい依存関係追跡でこれを自動化します。しかし、基礎となるメカニズムは、直接使用できるJavaScript機能に依存しています。
コアパターン:
- 追跡可能な構造に状態を格納する
- 状態が変更されたときにサブスクライバーに通知する
- 関連するDOMのみを更新する
ネイティブブラウザAPIは、外部依存なしで各ステップを処理します。
Proxyベースのリアクティブステート
Proxyオブジェクトは、プロパティのアクセスと代入をインターセプトします。Reflectと組み合わせることで、Proxyベースのリアクティブステートの基礎を形成します。
function createReactiveStore(initial, onChange) {
return new Proxy(initial, {
set(target, prop, value) {
const result = Reflect.set(target, prop, value)
onChange(prop, value)
return result
}
})
}
const state = createReactiveStore({ count: 0 }, (prop, value) => {
document.getElementById('count').textContent = value
})
state.count = 5 // DOMが自動的に更新される
このパターンは「シグナルのような」感覚です—状態に書き込むと、エフェクトが実行されます。Vue 3のリアクティビティシステムは、まさにこの理由で内部的にProxyを使用しています。
制限事項: Proxyトラップは、プロキシされたオブジェクト自体に適用された変更に対してのみ発火します。ネストされたオブジェクトや配列が独自のProxyでラップされていない場合、その内部の変更(array.push()など)は追跡されません。多くの開発者は、更新が確実にトリガーされるように、イミュータブルな更新(例:state.items = [...state.items, newItem])を使用します。
Pub/SubレイヤーとしてのEventTargetとCustomEvent
疎結合なコンポーネント通信には、EventTargetがネイティブなpub/subメカニズムを提供します。任意のオブジェクトをイベントエミッターにできます。
const bus = new EventTarget()
// サブスクライブ
bus.addEventListener('state-change', (e) => {
console.log('新しい値:', e.detail)
})
// パブリッシュ
bus.dispatchEvent(new CustomEvent('state-change', {
detail: { count: 10 }
}))
このパターンは、ネイティブブラウザAPIでリアクティブUIを実現します。コンポーネントはイベントをサブスクライブし、変更に反応し、疎結合を維持します。カスタムpub/sub実装とは異なり、EventTargetはブラウザDevToolsと統合され、標準的なイベントセマンティクスに従います。
Discover how at OpenReplay.com.
DOMリアクティビティのためのブラウザオブザーバー
状態だけでなく、DOMやレイアウトの変更に反応する必要がある場合、ブラウザオブザーバーがそのギャップを埋めます。
MutationObserverはDOM変更を監視します:
const observer = new MutationObserver((mutations) => {
mutations.forEach(m => console.log('DOMが変更されました:', m))
})
observer.observe(document.body, { childList: true, subtree: true })
IntersectionObserverは要素の可視性を追跡します—遅延読み込みや分析に便利です。
ResizeObserverはポーリングなしで要素のサイズ変更に応答します。
これらのAPIは長い間安定しており、本番環境で安全に使用できます。外部要因がDOMを変更する場合を処理することで、状態駆動型リアクティビティを補完します。
TC39 Signalsプロポーザル:今後の展望
リアクティビティプリミティブを標準化することへの関心が高まっています。TC39 Signalsプロポーザルは、フレームワークが共有できる共通モデルを定義することを目指しています。
重要: 2025年時点で、これはまだプロポーザルであり、出荷されたJavaScript機能ではありません。Solid、Angular、Preactなどのフレームワークはシグナルのようなパターンを採用しており、プロポーザルの設計に影響を与えています。しかし、今日のブラウザで「ネイティブシグナル」を使用することはできません。
上記のProxy + EventTargetパターンは同様の目標を達成します。シグナルが標準化されれば、メンタルモデルが一致しているため、移行は簡単なはずです。
適切なパターンの選択
| パターン | 最適な用途 | トレードオフ |
|---|---|---|
| Proxy | ローカルコンポーネントステート | ネストされた値もプロキシされない限り、プロキシされたオブジェクトの変更のみを追跡 |
| EventTarget | コンポーネント間メッセージング | 手動配線 |
| MutationObserver | 外部DOM変更への反応 | パフォーマンスオーバーヘッド |
小規模なアプリやウィジェットの場合、ProxyベースのステートとEventTargetを組み合わせることで、フレームワークのオーバーヘッドなしにほとんどのリアクティブUIニーズをカバーできます。
まとめ
フレームワークなしのリアクティビティは今日実用的です。Proxyは状態追跡を処理し、EventTargetはpub/subを提供し、ブラウザオブザーバーはDOM変更に反応します。これらのAPIは安定しており、十分に文書化されており、軽量なリアクティブコアに構成できます。
きめ細かいリアクティビティを得るためにフレームワークは必要ありません。必要なのは、フレームワークが構築されているプリミティブを理解することです—そして今、あなたはそれを理解しました。
よくある質問
Proxyトラップは、プロキシされたオブジェクトへの直接的なプロパティ代入に対してのみ発火します。ネストされたオブジェクトの場合、各ネストされたオブジェクトを独自のProxyで再帰的にラップするか、変更を加える際にネストされた構造全体を置き換える必要があります。ほとんどの開発者は、新しい参照を作成するためにスプレッド構文を使用するなど、イミュータブルな更新パターンを選択します。
EventTargetは、DevToolsと統合され、標準的なディスパッチセマンティクスに従うネイティブブラウザAPIです。完全なバブリングとキャプチャリングは、イベントターゲットがDOMツリーの一部である場合にのみ適用されます。カスタムライブラリは、ワイルドカードリスナーや一度だけのサブスクリプションなどの追加機能を提供する場合がありますが、EventTargetは依存関係を必要とせず、すべてのモダンブラウザで一貫して動作します。
外部コード、サードパーティスクリプト、またはブラウザ拡張機能によって行われたDOM変更に反応する必要がある場合は、MutationObserverを使用してください。Proxyは、制御しているJavaScript状態の変更を追跡します。MutationObserverは、変更の原因に関係なく、実際のDOMツリーを監視します。これらは異なる目的を果たし、しばしば連携して機能します。
Signalsプロポーザルは、フレームワークが共有できるリアクティビティプリミティブを標準化することを目指しており、既存のAPIを置き換えるものではありません。ProxyとEventTargetは有効なアプローチであり続けます。Signalsが出荷されれば、異なるライブラリ間できめ細かい依存関係追跡のための標準インターフェースを提供することで、これらのパターンを補完する可能性が高いです。
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.