見出し画像

JavaScript(ES2015~)でウィンドウサイズをシングルトンライクに管理する

こんにちは、AQUARING かに です。

ウィンドウのリサイズ時やCanvas表現の実装などでよく使う window.innerWidth, window.innerHeight ですが、みなさんウィンドウサイズのユーティリティクラスは作っていますか?
せっかくクラスで綺麗にカプセル化していても、ウィンドウサイズが必要になるたびに毎回 window の変数を取りに行くのはクラス外に依存する記述が増えてあまり良くないですよね。

そこで今回は、プロジェクト全体で共有するウィンドウサイズのユーティリティクラスをシングルトンパターンを真似て実装してみます。

Singleton パターン(シングルトン・パターン)とは、オブジェクト指向のコンピュータプログラムにおける、デザインパターンの1つである。(中略)Singleton パターンとは、そのクラスのインスタンスが1つしか生成されないことを保証するデザインパターンのことである。(Wikipediaより引用

まずJavaScriptでシングルトンライクに実装する2つの方法を見ていきましょう。

インスタンスのみをexportする方法

setter で名前を設定し、getter で自分の名前を返す Cat クラスを例にとって説明していきます。

まず通常のクラスの書き方だと以下のように定義したクラスを export する形なります。

class Cat {
  constructor() {
    this._name = 'noname';
  }
  
  set name(name) {
    this._name = name;
  }
  
  get name() {
    return this._name;
  }
}

export { Cat };
import { Cat } from './Cat';

const cat1 = new Cat();
cat1.name = 'tama';
console.log(cat1.name);

// 複数インスタンス生成できてしまう
const cat2 = new Cat();
cat2.name = 'mike';
console.log(cat2.name);

クラスそのものを export してしまうと、別ファイルから import していくらでもインスタンス生成ができてしまいます。

JavaScript では何を export するかを自分で決めることができるので、クラス自体は export せずに、ひとつだけ作ったインスタンスを export することで全体でひとつしかインスタンスが存在しないことを保証できます。

class Cat {
  constructor() {
    this._name = 'noname';
  }
  
  set name(name) {
    this._name = name;
  }
  
  get name() {
    return this._name;
  }
}

const cat = new Cat();

// インスタンスのみをexport
export { cat };
// インスタンスをimport
import { cat } from './Cat';

cat.name = 'tama';
console.log(cat.name);// >> 'tama'

クラスそのものを export しないことで別ファイルからクラスを import できず、インスタンス生成ができなくなります。
もちろん同一ファイル内であれば複数インスタンスを生成できてしまいますが、自分でシングルトンとしてコードを書いているのであればこれで十分ですね。

メンバをすべてstaticにする方法

上記のインスタンスのみをexportする方法では、クラスの記述は変更せずexportを工夫することによって全体でひとつしかインスタンスが存在しないことを保証していました。

逆にクラス側を工夫してシングルトンのような動作をさせるには、メンバ変数 / メンバ関数をすべて static にしてインスタンスを生成する必要がないようにします。

class Cat {
  static _name = 'noname';
  
  static set name(name) {
    this._name = name;
  }
  
  static get name() {
    return this._name;
  }
}

export { Cat };
import { Cat } from './Cat';

Cat.name = 'tama';
console.log(Cat.name); // >> 'tama'

メンバ変数を static にすると、インスタンスが複数あっても同じクラス内でひとつの変数を共有することができます。
static メンバはインスタンスを生成しなくても使うことができるので、全てのメンバを static にすることで、全体でひとつしかインスタンスが存在しないのと同じような動作になります。

こちらも厳密にはシングルトン(インスタンスが1つしか生成されないことを保証するもの)ではありませんが、目的の動作は保証できます。

ウィンドウサイズのユーティリティクラス

ふたつめに紹介したメンバをすべて static にする方法で実装したウィンドウサイズのユーティリティクラスがこちらです。

/**
 * ウィンドウサイズ取得用の静的クラス
 */
class ScreenUtil {
  static _size = {
    x: window.innerWidth,
    y: window.innerHeight
  };

  /**
   * ウィンドウサイズ
   * @type {{ x: number, y: number }}
   */
  static get size() {
    return Object.assign({}, this._size);
  }

  /**
   * ウィンドウの幅
   * @type {number}
   */
  static get width() {
    return this.size.x;
  }

  /**
   * ウィンドウの高さ
   * @type {number}
   */
  static get height() {
    return this.size.y;
  }

  /**
   * リサイズ
   */
  static resize() {
    Object.assign(this._size, {
      x: window.innerWidth,
      y: window.innerHeight
    });
  }
}

export { ScreenUtil };
import { ScreenUtil } from 'ScreenUtil';

window.addEventListener('resize', () => {
  ScreenUtil.resize();
  console.log(ScreenUtil.width);
});

ScreenUtil を使う前に _size が更新されている必要があるので、 ScreenUtil.resize() をリサイズイベントの一番最初に呼ぶ必要があります。

ここでは _size をプロパティ x, y を持つオブジェクトとして書いていますが、three.js などのベクトルクラスが実装されているライブラリを使用する場合は {x, y} を THREE.Vector2 などに置き換えたほうが使いやすくなります。

さいごに

今回は割愛しましたが、サイズの他にもウィンドウの中心座標や画面のピクセル比など、よく使うものを適宜 getter で定義しておくとより便利になります。
ウィンドウサイズの他にもプロジェクト全体でひとつを共有したいクラスはいろいろあると思うので、そんなときはぜひシングルトンで書いてみましょう。

この記事が気に入ったらサポートをしてみませんか?