たくさん(> 50)の小さな WebComponents が相互作用するWebアプリがあります。
すべてを切り離しておくために、原則として、コンポーネントが別のコンポーネントを直接参照することはできません。代わりに、コンポーネントがイベントを発生させ、(「メイン」アプリ内で)配線されて、別のコンポーネントのメソッドを呼び出します。
時間が経つにつれ、追加されるコンポーネントが増え、「メイン」のアプリファイルには次のようなコードチャンクが散らばっています。
buttonsToolbar.addEventListener('request-toggle-contact-form-modal', () => {
contactForm.toggle()
})
buttonsToolbar.addEventListener('request-toggle-bug-reporter-modal', () => {
bugReporter.toggle()
})
// ... etc
これを改善するために、類似の機能をClass
にグループ化し、関連性のある名前を付け、インスタンス化時に参加要素を渡し、Class
内の「配線」を処理します。
class Contact {
constructor(contactForm, bugReporter, buttonsToolbar) {
this.contactForm = contactForm
this.bugReporterForm = bugReporterForm
this.buttonsToolbar = buttonsToolbar
this.buttonsToolbar
.addEventListener('request-toggle-contact-form-modal', () => {
this.toggleContactForm()
})
this.buttonsToolbar
.addEventListener('request-toggle-bug-reporter-modal', () => {
this.toggleBugReporterForm()
})
}
toggleContactForm() {
this.contactForm.toggle()
}
toggleBugReporterForm() {
this.bugReporterForm.toggle()
}
}
そして、次のようにインスタンス化します。
<html>
<contact-form></contact-form>
<bug-reporter></bug-reporter>
<script>
const contact = new Contact(
document.querySelector('contact-form'),
document.querySelector('bug-form')
)
</script>
</html>
私は自分自身のパターンを導入することに本当にうんざりしています。特に、より良いWordがないため、Classes
を単なる初期化コンテナーとして使用しているため、実際にはOOP-yではないパターンを導入することにうんざりしています。
私が見逃しているこのタイプのタスクを処理するためのよりよく知られた定義済みのパターンはありますか?
あなたが持っているコードはかなり良いです。少し不快に思えるのは、初期化コードがオブジェクト自体の一部ではないということです。つまり、オブジェクトをインスタンス化できますが、そのワイヤリングクラスの呼び出しを忘れると、そのオブジェクトは役に立たなくなります。
通知センター(別名イベントバス)が次のように定義されているとします。
class NotificationCenter(){
constructor(){
this.dictionary = {}
}
register(message, callback){
if not this.dictionary.contains(message){
this.dictionary[message] = []
}
this.dictionary[message].append(callback)
}
notify(message, payload){
if this.dictionary.contains(message){
for each callback in this.dictionary[message]{
callback(payload)
}
}
}
}
これはDIYのマルチディスパッチイベントハンドラです。その後、コンストラクタの引数としてNotificationCenterを要求するだけで、独自の配線を行うことができます。そこにメッセージを送信し、ペイロードを渡すのを待つのは、システムとの唯一の連絡先なので、非常にしっかりしています。
class Toolbar{
constructor(notificationCenter){
this.NC = notificationCenter
this.NC.register('request-toggle-contact-form-modal', (payload) => {
this.toggleContactForm(payload)
}
}
toolbarButtonClicked(e){
this.NC.notify('toolbar-button-click-event', e)
}
}
注:質問で使用されているスタイルと一致させるため、および簡単にするために、キーにインプレース文字列リテラルを使用しました。タイプミスのリスクがあるため、これはお勧めできません。代わりに、列挙または文字列定数の使用を検討してください。
上記のコードでは、ツールバーは、関心のあるイベントのタイプをNotificationCenterに通知し、notifyメソッドを介してすべての外部インタラクションを公開します。 toolbar-button-click-event
に関心のある他のクラスは、コンストラクターに登録するだけです。
このパターンの興味深いバリエーションは次のとおりです。
興味深い機能は次のとおりです。
興味深い落とし穴と可能な対策は次のとおりです。
以前は、ある種の「イベントバス」を導入していましたが、UIコードのイベントを伝達するために、ドキュメントオブジェクトモデル自体にますます依存するようになりました。
ブラウザでは、DOMは、ページのロード中であっても常に存在する1つの依存関係です。重要なのは、JavaScriptで カスタムイベント を利用し、イベントバブリングを利用してそれらのイベントを伝達することです。
サブスクライバーを接続する前に、「ドキュメントの準備ができるのを待つ」と叫ぶ前に、document.documentElement
プロパティは、スクリプトがインポートされた場所や順序に関係なく、JavaScriptが実行を開始した瞬間から<html>
要素を参照しますマークアップに表示されます。
ここからイベントのリスニングを開始できます。
ページ上の特定のHTMLタグ内にJavaScriptコンポーネント(またはウィジェット)を配置することは非常に一般的です。コンポーネントの「ルート」要素は、バブリングイベントをトリガーできる場所です。 <html>
要素のサブスクライバーは、他のユーザーが生成したイベントと同様に、これらの通知を受け取ります。
ボイラープレートコードの例:
(function (window, document, html) {
html.addEventListener("custom-event-1", function (event) {
// ...
});
html.addEventListener("custom-event-2", function (event) {
// ...
});
function someOperation() {
var customData = { ... };
var event = new CustomEvent("custom-event-3", { detail : customData });
event.dispatchEvent(componentRootElement);
}
})(this, this.document, this.document.documentElement);
したがって、パターンは次のようになります。
document.documentElement
プロパティでこれらのイベントをサブスクライブします(ドキュメントの準備が整うまで待つ必要はありません)。document.documentElement
でイベントを公開します。これは、関数型とオブジェクト指向の両方のコードベースで機能します。
Unity 3Dを使用したビデオゲーム開発でも同じスタイルを使用しています。 Health、Input、Stats、Soundなどのコンポーネントを作成し、それらをゲームオブジェクトに追加して、そのゲームオブジェクトを構築します。 Unityには、コンポーネントをゲームオブジェクトに追加するメカニズムがすでにあります。しかし、私が見つけたのは、ほとんどの人がコンポーネントを照会したり、他のコンポーネント内のコンポーネントを直接参照していることです(それらがインターフェイスを使用していたとしても、私が好むおかげでさらに結合されています)。他のコンポーネントとの依存関係がまったくなく、コンポーネントを分離して作成できるようにしたいそのため、データが変更されたときにコンポーネントにイベントを発生させ(コンポーネントに固有)、基本的にデータを変更するメソッドを宣言しました。次に、ゲームオブジェクトでクラスを作成し、すべてのコンポーネントイベントを他のコンポーネントメソッドに接着しました。
私がこれについて気に入っているのは、ゲームオブジェクトのコンポーネントのすべての相互作用を確認するために、この1つのクラスだけを見ることができるということです。 Contactクラスは私のゲームオブジェクトクラスとよく似ているようです(MainPlayer、Orcなどのオブジェクトにゲームオブジェクトの名前を付けます)。
これらのクラスは、一種のマネージャークラスです。コンポーネント自体には、コンポーネントのインスタンスとそれらを接続するコード以外は何もありません。ここで、直接接続できる他のコンポーネントメソッドを呼び出すだけのメソッドを作成する理由がわかりません。このクラスのポイントは、実際にはイベントのフックアップを整理することだけです。
イベント登録の補足として、filterコールバックとargsコールバックを追加しました。イベントがトリガーされると(私は独自のカスタムイベントクラスを作成しました)、フィルターコールバックが存在する場合はコールバックし、trueを返す場合はargsコールバックに移動します。フィルターコールバックのポイントは、柔軟性を与えることでした。イベントはさまざまな理由でトリガーされる可能性がありますが、チェックがtrueの場合にのみフックされたイベントを呼び出します。例として、OnKeyHitイベントを持つ入力コンポーネントがあります。 MoveForward()MoveBackward()などのメソッドを持つMovementコンポーネントがある場合、OnKeyHit + = MoveForwardをフックできますが、キーを押して前方に移動したくありません。キーが「w」キーの場合にのみ、それを実行します。 OnKeyHitは渡される引数を入力していて、そのうちの1つがヒットしたキーであるため、フィルターコールバック内でそれを確認し、「w」がtrueを返す場合はfalseを返します。
私にとって、特定のゲームオブジェクトマネージャークラスのサブスクリプションは次のようになります。
input.OnKeyHit.Subscribe(movement.MoveForward, (args) => { return args.Key == 'w' });
コンポーネントは分離して開発できるため、複数のプログラマーがそれらをコーディングすることができます。上記の例では、入力コーダーは引数オブジェクトにKeyという名前の変数を与えました。ただし、Movementコンポーネントの開発者はKeyを使用していなかった可能性があります(引数を調べる必要がある場合、この場合はおそらくそうではありませんが、渡された引数の値を使用します)。この通信要件を削除するために、argsコールバックはコンポーネント間の引数のマッピングとして機能します。したがって、このゲームオブジェクトマネージャークラスを作成する人は、2つのクライアントを接続してその時点でマッピングを実行するときに、2つのクライアント間のarg変数名を知る必要があるだけです。このメソッドは、フィルター関数の後に呼び出されます。
input.OnKeyHit.Subscribe(movement.MoveForward, (args) => { return args.Key == 'w' }, (args) => { args.keyPressed = args.Key });
したがって、上記の状況では、入力担当者はargsオブジェクト内の変数に「Key」という名前を付けましたが、Movementでは「keyPressed」という名前にしました。これは、コンポーネントが開発されているときにコンポーネント自体をさらに分離し、マネージャークラスの実装者に配置して、正しく接続するのに役立ちます。
価値があることについて、私はバックエンドプロジェクトの一部として何かをしており、同様のアプローチをとっています。
あなたの建設/配線の課題への対処方法:
ツールバーとの類似性:
直面する可能性のある問題:
.filter
状態に応じて(実際には、実装はより機能的です。会話は、状態変化イベントのオブザーバブルからのオブザーバブルのフラットマップです)。したがって、要約すると、問題のドメイン全体をオブザーバブルとして、または「機能的に」/「宣言的に」見て、バス(オブザーバブル)から派生したバス(オブザーバブル)から、イベントコンポーネントとしてオブザーバーとしてWebコンポーネントを検討できます。オブザーバー)も購読しています。オブザーバブルのインスタンス化(例:新しいツールバー)は宣言的であり、プロセス全体をオブザーバブルのオブザーバブルと見なすことができます.map
'd入力ストリームから。