LoginSignup
31
40

概要

この記事では、ライブラリやフレームワークに依存せず、純粋なJavaScriptのみを使用してデータバインディングを実装する方法について詳しく説明します。効率的なWebアプリケーションの開発に欠かせない基本的な技術を、具体的な例を交えて紹介します。JavaScriptのさらなる可能性に興味がある初心者から中級者の方々にとって、理想的な内容です。

検証方法

データバインディングの検証を行うために、次の機能を持つツールを作成します。

  • クリック回数を表示するテキストエリアを用意します。
  • ボタンをクリックするごとに、表示されているクリック回数を1増やします。

実装案

  • 純粋なJavaScriptを使用して、データバインディングの制御を担うモジュールを開発します。
  • このデータバインディング制御モジュールを使用して、クリック回数表示ツールを実装します。

データバインディング制御モジュールの設計

独自に開発したイベント管理ツールを用いて、データバインディングの設定とその変更を監視します。

イベントツール

ソース

class EventEmitter {
  constructor() {
    this.listeners = new Map();
  }

  // イベントリスナーを追加
  on(event, listener, options = {}) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    const listenerObj = { listener, options };
    this.listeners.get(event).push(listenerObj);

    // AbortSignalによるリスナー削除のサポート
    if (options.signal) {
      options.signal.addEventListener('abort', () => {
        this.off(event, listener);
      }, { once: true });
    }
  }

  // イベントを発生させ、リスナーを呼び出す
  emit(event, data) {
    const eventListeners = this.listeners.get(event);
    if (eventListeners) {
      eventListeners.forEach(({ listener }) => {
        listener(data);
      });
    }
  }

  // イベントリスナーを削除
  off(event, listenerToRemove) {
    if (this.listeners.has(event)) {
      const filteredListeners = this.listeners.get(event).filter(({ listener }) => listener !== listenerToRemove);
      this.listeners.set(event, filteredListeners);
    }
  }
}

ソース説明

このEventEmitterクラスは、カスタムイベントの作成と管理を可能にするための簡単なフレームワークを提供します。クラスの各メソッドは、イベントリスナーの登録、イベントの発生時のリスナー呼び出し、およびリスナーの削除を行うために使用されます。このクラスは、より高度なイベント処理機能、特にAbortSignalを用いたリスナーの削除のサポートを含む、JavaScriptのイベント処理パターンを実装しています。

データバンディングモジュール

ソース

class DataBinder extends EventEmitter {
  constructor(el, data) {
    super();
    this.el = el;
    this.data = new Proxy(data, {
      set: (target, property, value) => {
        const result = Reflect.set(target, property, value);
        this.emit('change', { property }); // 変更イベントを発火
        return result;
      }
    });

    this.bindChildren();
  }

  bindChildren() {
    const elements = this.el.querySelectorAll('[bind-text]');
    elements.forEach(element => {
      const propName = element.getAttribute('bind-text');
      if (this.data[propName] !== undefined) {
        element.innerText = this.getter(this.data[propName]);
        this.on('change', (e) => {
          if (e.property === propName) {
            element.innerText = this.getter(this.data[propName]);
          }
        });
      }
    });
  }

  // getter関数を追加
  getter(value) {
    return `{{ ${value} }}`;
  }
}

ソース説明

このソースコードは、EventEmitterクラスを継承したDataBinderクラスを実装しています。これにより、特定のデータオブジェクトの変更を監視し、それに応じてDOMを更新する機能を提供します。JavaScriptのProxyを用いてデータオブジェクトに変更があった際にイベントを発火し、DOM要素の内容をデータと同期させるようにしています。このパターンは、双方向データバインディングを実現するための基礎となります。

検証用ツール

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>databinding検証</title>
  <script src="./databind/event_emitter.js"></script>
  <script src="./databind/data_bind.js"></script>
  <script>
    document.addEventListener('DOMContentLoaded', function() {
      const app = document.getElementById('app');
      let data = { count: 0 }; // countをdataオブジェクトのプロパティとして宣言

      // データバインディングモジュールを使用
      const binder = new DataBinder(app, data);

      // カウントアップイベントの処理
      btn = app.querySelector('button');
      btn.addEventListener('count-up', () => {
        data.count++;
        binder.emit('change', { property: 'count' }); // 変更を通知
      });
    }, false);
  </script>
</head>
<body>
<div id="app">
  <p bind-text="count">カウント: {{count}}</p>
  <button type="button" onclick="this.dispatchEvent(new CustomEvent('count-up'))">カウントアップ</button>
</div>
</body>
</html>  
31
40
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
40