モーダル実装から学ぶクラス設計 – JavaScriptコードの再利用性を高める

JavaScriptのモーダル実装、あなたのコードは大丈夫ですか?手続き型で書かれたコードは、開発が進むにつれてメンテナンスの悪夢となりかねません。

この記事では、実際のモーダルウィンドウの実装を例に、クラスベースの設計がもたらす明確な利点を解説。コードの価値を高める具体的な改善方法をお伝えします。

よくあるモーダル実装の課題

Webアプリケーション開発において、モーダルウィンドウは最も一般的なUIコンポーネントの一つです。シンプルな機能に見えますが、実際の開発では様々な課題に直面します。

典型的な課題として:

  • 状態管理の複雑化
  • イベントリスナーの適切な管理
  • 複数モーダルの競合
  • アクセシビリティへの対応
  • アニメーションの実装

多くの開発者は手続き型のコードで実装を始めますが、要件の追加とともにコードは複雑化していきます。

では、実際のコードを見ながら、どのように改善できるか考えていきましょう。

手続き型で書かれたシンプルなモーダル実装

See the Pen B-modal_sample by R (@rkpg) on CodePen.

まずは、多くの開発者が最初に書くような手続き型のコードを見てみましょう。

JS
const modal = document.querySelector('.modal');
const openButton = document.querySelector('.open-modal');
const closeButton = document.querySelector('.close-modal');

function openModal() {
  modal.style.display = 'block';
  document.body.style.overflow = 'hidden';
}

function closeModal() {
  modal.style.display = 'none';
  document.body.style.overflow = 'auto';
}

openButton.addEventListener('click', openModal);
closeButton.addEventListener('click', closeModal);

一見シンプルで分かりやすいコードですが、以下のような問題を抱えています:

  1. グローバルスコープの汚染
  2. 機能の拡張が難しい
  3. 再利用性が低い
  4. 状態管理が不透明

この実装では、モーダルの表示/非表示の切り替えは実現できていますが、実際のプロジェクトではより多くの機能が必要になることがほとんどです。次のセクションでは、これらの課題をクラスベースの設計でどのように解決できるのかを見ていきましょう。

クラスベースの改善されたモーダル実装

See the Pen B-modal-sample_02 by R (@rkpg) on CodePen.

先ほどの手続き型のコードをクラスベースに改善してみましょう。

JS
class Modal {
  constructor() {
    this.modal = document.querySelector('.modal');
    this.openButton = document.querySelector('.open-modal');
    this.closeButton = document.querySelector('.close-modal');
    this.bindEvents();
  }

  bindEvents() {
    this.openButton.addEventListener('click', () => this.open());
    this.closeButton.addEventListener('click', () => this.close());
  }

  open() {
    this.modal.style.display = 'block';
    document.body.style.overflow = 'hidden';
  }

  close() {
    this.modal.style.display = 'none';
    document.body.style.overflow = 'auto';
  }
}

// 使用
const modal = new Modal();

このクラスベースの実装では、以下の改善が実現されています:

  1. 状態管理の明確化: isOpenフラグによってモーダルの状態を追跡
  2. カプセル化: モーダルに関連する機能を1つのクラスにまとめる
  3. 機能拡張: ESCキーでの閉じる、外側クリックでの閉じるなどの機能を追加
  4. メモリリーク防止: イベントリスナーの適切な管理
  5. 再利用性: 複数のモーダルに対して同じコードを使い回せる

クラスベースの実装がもたらすメリット

シンプルな手続き型からクラスベースへの移行で、以下のような具体的なメリットが得られます:

1.コードの整理と管理

  • 関連する機能が1つのクラスにまとまる
  • 変数や関数のスコープが明確になる

2.再利用性の向上

JS
const modal1 = new Modal('.modal-1');
const modal2 = new Modal('.modal-2');

3.拡張性

JS
class Modal {
  // 既存のメソッドに加えて...
  addContent(content) {
    const contentDiv = this.modal.querySelector('.modal-content');
    contentDiv.innerHTML = content;
  }
  
  setPosition(x, y) {
    this.modal.style.left = `${x}px`;
    this.modal.style.top = `${y}px`;
  }
}

クラスベースの設計により、新機能の追加や既存機能の修正が容易になり、コードの保守性が大幅に向上します。さらに、テストも書きやすくなるというメリットもあります。

クラスを使うべき時と使わない時の判断基準

クラスを使うべき場合

  1. 複数のインスタンスが必要
  2. 状態管理が重要
  3. 将来的な機能追加の予定がある
  4. コードの再利用性を高めたい

クラスを使わない方が良い場合

  1. 単一のシンプルなモーダル
  2. 状態管理が不要
  3. 機能が固定的
  4. 小規模なプロジェクト
JS
// シンプルな実装で十分な例
const toggleModal = () => {
  const modal = document.querySelector('.modal');
  modal.style.display = modal.style.display === 'none' ? 'block' : 'none';
};

プロジェクトの規模や要件に応じて、適切な実装方法を選択することが重要です。過剰な設計は避け、必要に応じてリファクタリングすることをお勧めします。

まとめ:より良いコード設計への第一歩

クラスベースの実装は、単にコードを書き換えることが目的ではありません。これは、より保守性が高く、拡張性のあるコードを目指す第一歩です。

ポイントをまとめると:

1.設計の重要性

  • 手続き型からクラスベースへの移行は、コードの質を向上させる良い機会
  • 適切な粒度でのリファクタリングが重要

2.実践的な判断基準

  • プロジェクトの規模と要件に応じた実装選択
  • オーバーエンジニアリングを避ける

3.今後の発展

JS
class Modal {
  // 将来的な機能追加の例
  animate() { /* アニメーション処理 */ }
  validate() { /* バリデーション処理 */ }
  setTheme(theme) { /* テーマ切り替え */ }
}

この記事で紹介したモーダルウィンドウの例は、日々の開発で直面する設計改善の一例です。同じ考え方を他のUIコンポーネントにも応用していくことで、より良いコードベースを築いていけるでしょう。