目次
JavaScipr

JavaScript中級テクニック【CustomEvent】でオブジェクト指向による設計を活かしたい

こんにちは、皆さん。Notedでメンバーでエンジニアとして活動しているSoraY677(Raxsy)です!

急ですが最近巷でよく聞きませんか?「オブジェクト指向はオワコン」だと。今回はこの問題について言及しつつ、JavaScriptのCustomEventを用いてオブジェクト指向を活かせないか、その提案をさせていただきます!

オブジェクト指向は本当にオワコン🤔?

確かにオブジェクト指向はとっつきにくいところがあるのは否めません。オブジェクト同士の関係性が複雑になれば分かりづらいですし、そもそも依存性の少ない関数型プログラミングで事足りるような要件であればそれで十分だなと自身も思います。特にJavaScriptで言えば、関数型主体で十分なときは多々あります。

ただし、どうでしょう。フロントエンドで使われるJavaScriptで言えば、その特徴的な機能の一つにDOMの操作があるわけですね。

例えば、HTMLで <input id="sample" type="hidden" value=3/>という要素があるとき、JavaScriptで、

document.querySelector('input#sample').value = 100;

とすればvalueを100に変更できるわけですよね。理屈は非常にシンプルです。

しかし、この操作一つによって開発者が気にしないといけないスコープは非常に広いわけです。

この値を他の箇所から参照している可能性があるわけですし。どんな値に書き換えられるかはもちろん、DOM自体がそもそも消せてしまうのでちゃんと存在しているかや、コードのある箇所で別の属性が追加される可能性があるなら、それがあるかも気にしないといけないですよね。DOMの操作は基本的な動作なので、ほとんどのサイトは導入していることでしょう。

おっと、困りましたね。このままでは非常に保守の難しいシステムになってしまう…

そこで僕は、オブジェクト指向の考え方とJSの便利な機能である【CustomEvent】を組み合わせてこの問題を解決する方法を提案させていただきます!

CustomEventを使ってオブジェクト指向を活かす!

さきほどの問題の核として、ある特定のDOM操作の役割を不特定多数が負ってしまっているというところにあるのかなと思います。

これを解決するには、「ルールとして、特定のDOMの操作にはそこに紐づいた唯一のクラスからのみ可能にする」とした上で、「そのクラスを単一の箇所のみでインスタンス化」してやれば、DOM操作に干渉できるのが一箇所のみとなって解決できるわけです💪これは正しくオブジェクト指向の考え方(カプセル化)なわけですね。

ちょっとイメージ掴みづらいと思いますが、詳細にお話する前に、そもそもCustomEventについて少しお話しますね。

CustomEventって?

カスタムイベントは一言で言えば自作イベントを作れる機能です。皆さんも以下のような構文を見たことないですか?

document.querySelector('button#sample').addEventListener('click', () => {
    window.alert('ボタンが押されたよ')
})

これは <button id="sample">サンプルボタン</button> のようなボタンが押された際にアラート表示を行うだけの処理です。

これは標準で用意されているclickイベントをボタンに登録することで、イベント内の処理がボタンクリック時に行われるというものです。

CustomEventでは、これを自分で定義・発火することができるわけです。

https://developer.mozilla.org/ja/docs/Web/API/CustomEvent/CustomEvent

使い方を抜粋すると、以下のようになります。

// サンプルイベント
const sampleEvent = new CustomEvent('sample-event', {
  detail: {
    name: 'sampleイベントだよ~'
  }
});

// イベント登録
window.addEventListener('sample-event', (e) => alert(e.detail.name) );

// イベント発火 
window.dispatchEvent(sampleEvent);

このコード的には「sampleイベントだよ~」というアラートを出すだけなのですが、要はこのようにして自分でイベントを定義・発火させられるわけです。

ちなみにCanIuse調べた結果は以下の通りで、古いバージョンでもほとんど対応しています。

https://caniuse.com/?search=CustomEvent

ではこれをオブジェクト指向にどのように活かすのかお話しします!

オブジェクト指向 x CustomEvent

さて、それでは本題です。

どうやってオブジェクト指向をCustomEventと組み合わせて保守性の高いコードを開発するのか見ていきます。

まずは特定のDOMに紐づくクラスを用意

まず、先程話した通り、特定のDOMに紐づくクラスを用意してみます。

class SampleDom {
    dom: HTMLInputElement
    constructor() {
        this.dom = document.querySelector('input#sample-input')
    }
}

はい。そして、今から input#sample-input 要素には、このクラスからしかアクセスできないということにしましょう。他のクラスで document.querySelector('input#sample-input') を実行するのは禁止です。よし、堅牢になりました…やった!

しかし、ここで一つ問題になるのは、例えば別箇所の button#sample-button が押された場合は現在の input#sample-inputのvalueを10に書き換えてアラート表示したいとしましょう。

すると、まぁもちろんSampleDomが input#sample-input を隠しているので、アラート表示をする処理を外側の button#sample-button から直接行うのは無理なのです。

そこで使うのがCustomEventなんですね。

CustomEventを使ってイベント受付口を作る

まずはイベントを用意してやります。

ここが今回の肝です。

const alertActivateEvent = new CustomEvent('alert-activate', {
  detail: {
      value: 10
  }
});

次に先程用意したSampleDomのクラスに対して、このイベントが送られた際に input#input-sample のvalueを10に書き換えてアラート表示するというイベント受付口を加えてやります。

class SampleDom {
    constructor() {
        // ...
        window.addEventListener('alert-activate', (e) => {
            this.dom.value = e.detail.value
            alert(this.dom.value)
        } );
    }
}

最後にボタンがクリックされた際にこのイベントを発火させてやるようにします。

document.getElementById('button#sample-button').addEventListener('click', () => {
    window.dispatchEvent(alertActivateEvent);
})

すると、ボタンが押される => イベントが飛ぶ => SampleDomがイベントを検知 => アラートが出るという流れが出来上がりました!意図した通りの動きをするはずです。

ここまでで皆さん気づきましたか?この実装により、特定のイベントで特定のDOM操作しか行われないではありませんか!そのため、気にしないといけないことは非常に減りましたよね?DOMが誤って削除されるなどという可能性はぐんと下がっているはずです!

また、副次的な効果としてですが、状態管理も成立していることにお気づきでしょうか?

特定の変更を複数のDOMに対して行いたいのであれば、イベント受付口の用意を該当するDOMを管理するクラスに対して行ってあげればよいわけです。こうすることにより、関数型では面倒だった一斉に状態を変更するという問題を解決しています。

おわりに

というわけで以上、オブジェクト指向とCustomEventを組み合わせて、保守性の高い仕組みを提案してみました!いかがでしたか?

ちなみにこの仕組みは現在Notedで開発している「字幕くん」というアプリケーションに採用しています。乞うご期待!

ではまた別の記事で会いましょう~👋

目次
この記事をシェア
この記事に含まれるタグ