こんにちは、Ryohei(@ityryohei)です!

モダンなJavaScript開発において、「データが変更されたら、自動で画面を更新する」というリアクティブな状態管理は必須の技術となっています。しかし、従来のJavaScriptでは、オブジェクトの変更を監視するためには、Setter/Getterを大量に定義したり、Object.definePropertyを使う必要があり、コードが複雑になるのが課題でした。

本記事では、Vue.jsなどの人気フレームワークの根幹を支えるProxy APIを徹底解説します。Proxyを使うことで、あなた自身のアプリケーションでシンプルかつ強力なリアクティブシステムを実装する方法を紹介します。

データを監視して、変更されたら自動でDOMを更新したいんだけど、フレームワークなしでやるのはコードが複雑になりそうで手がつけられないんだ。もっとエレガントな方法はないかな?

上記の疑問にお答えします。

では、解説していきます。

Proxy APIとは

従来のJavaScriptでオブジェクトの変更を監視する手法には、多くの限界がありました。

従来のデータ監視の問題点

  1. Object.defineProperty()の限界:
    プロパティを一つずつ定義する必要があり、冗長です。さらに、後から追加されたプロパティや、配列操作(pushやpop)の変更を検知できません。
  2. Setter/Getterの限界:
    プロパティの削除(delete)といった操作は監視できませんでした。

これらの問題のため、「データが変わったら自動的に画面が変わる」というリアクティブなシステムをVanilla JSで組むのは非常に困難でした。

Proxy APIの原理と優位性

Proxy(プロキシ)APIは、ターゲットとなるオブジェクトと、そのオブジェクトへのアクセス(操作)をフックするハンドラーの間に立ちます。まるで「門番」のように機能します。

Proxyは、オブジェクトに対するあらゆる操作(読み取り、書き込み、削除など)を検知・横取りできます。この能力のおかげで、データのリアクティブ化が非常にシンプルになりました。

基本的な使い方!Proxyの作成とコアなトラップ

Proxyのインスタンスを作成する手順と、最も重要な監視メソッド「トラップ」について見ていきましょう。

ステップ1: Proxyの作成

Proxyは、監視したいターゲットオブジェクトと、監視のルールを定義するハンドラーオブジェクトの二つを引数にとって作成します。

JavaScript

const targetObject = { count: 0, user: 'Hoge' };
const handlerObject = {}; // トラップ(フック)を定義する空のオブジェクト

const proxyObject = new Proxy(targetObject, handlerObject);

ステップ2: コアなトラップ(set / get)の概念

ハンドラー内に定義するメソッドは「トラップ」と呼ばれます。

トラップ名意味実行されるタイミング
getデータの「読み取り」を横取りするproxy.count のように、プロパティにアクセスしたとき
setデータの「書き込み」を横取りするproxy.count = 1 のように、プロパティの値を変更したとき

JavaScript

const target = { count: 0 };

const handler = {
  // 値が読み込まれたとき
  get(target, key) {
    console.log(`[GET] ${key}が読み込まれました。`);
    return target[key];
  },

  // 値が変更されたとき
  set(target, key, value) {
    console.log(`[SET] ${key}が ${target[key]} から ${value} に変更されます。`);
    // ターゲットに実際の値を設定
    target[key] = value;
    return true; // 成功を返す
  }
};

const reactiveData = new Proxy(target, handler);

これで、reactiveData経由でアクセスするだけで、アクセスログを取ったり、値をチェックしたりといった操作ができるようになりました。

実践!リアクティブな状態管理ストアの実装方法

このProxyの仕組みを使って、データが変更されたら自動的に画面のDOMを更新する簡易的な状態管理ストアを実装してみましょう。

HTML

<div id="app">
  <h1>カウンター:<span id="counter-display">0</span></h1>
  <button id="increment-button">増やす</button>
</div>

JavaScript

const state = {
  count: 0
};

// データを監視し、変更されたらDOMを更新するハンドラー
const reactiveHandler = {
  set(target, key, value) {
    // 実際のデータを更新(Reflectを使うのが安全です)
    const success = Reflect.set(target, key, value);

    // keyが 'count' だったら画面を更新
    if (key === 'count') {
      const display = document.getElementById('counter-display');
      
      if (display) {
        display.textContent = value;
      }
    }
    
    // 値の変更が成功したことを返す
    return success;
  }
};

// Proxyオブジェクトを作成し、リアクティブなデータとして使用
const store = new Proxy(state, reactiveHandler);

// 初期表示
document.getElementById('counter-display').textContent = store.count;

// ボタンクリックでデータを更新
document.getElementById('increment-button').addEventListener('click', () => {
  // Proxy経由でデータを変更すると、set トラップが発動し、画面が自動更新されます!
  store.count++;
});

応用活用と補足!高度な使い方と実務の知識

setget以外にも、Proxyは高度な機能を提供しており、あなたのコードの信頼性を高めるのに役立ちます。

データ検証(Validation)の実装

setトラップの中で、値が正しいか検証し、問題があれば更新を拒否できます。

JavaScript

const validationHandler = {
  set(target, key, value) {
    // 'age' プロパティには数値しか設定できないように制限
    if (key === 'age' && typeof value !== 'number') {
      console.error('エラー: ageには数値のみ設定可能です。');
      return false; // 更新を拒否し、処理を中断
    }
    return Reflect.set(target, key, value);
  }
};

const validatedObject = new Proxy({ age: 20 }, validationHandler);
validatedObject.age = 'twenty'; // -> エラー出力、値は変更されない
validatedObject.age = 21;      // -> 成功

Reflect APIの活用(補足)

コード内で使ったReflect.set()は、Proxyのハンドラー内でのプロパティ操作を安全かつ標準的に行うためのAPIです。Proxyを使うときは、トラップ内の実際の操作にReflectを使うのがモダンな書き方だと覚えておきましょう。

ブラウザ対応状況とPolyfill(まとめ)

Proxyは比較的新しいAPIですが、現在、主要なモダンブラウザ(Chrome, Firefox, Safari, Edge)では完全にサポートされています。そのため、最新の開発環境ではPolyfillなしでそのまま利用可能です。もし古い環境への対応が必要な場合は、WICGが提供するPolyfillを利用することで対応が可能です。

最後に

Proxy APIは、Webアプリケーションのデータ管理におけるアプローチを大きく変える強力な機能です。

  • 本質的な解決:
    従来のSetter/GetterやObject.definePropertyの限界を乗り越え、オブジェクトに対するあらゆる操作を網羅的に監視できます。
  • リアクティブな基盤:
    フレームワークを使わずとも、データとビューを連動させるリアクティブな状態管理の基礎を築くことができます。

Proxy APIを使いこなせば、あなたのJavaScriptコードはよりクリーンで、よりモダンな設計へと進化するでしょう。

以上、JavaScriptのProxy APIを使ったモダンな状態管理のご紹介でした!

この記事を書いた人

Ryohei

Webエンジニア / ブロガー

福岡のWeb制作会社に務めるWebエンジニアです。エンジニア歴は10年程で、好きな言語はPHPとJavaScriptです。本サイトは私がインプットしたWebに関する知識を整理し、共有することを目的に2015年から運営しています。Webに関するご相談があれば気軽にお問い合わせください。