イベント
イベントは、要素が変更を伝える標準的な方法です。これらの変更は通常、ユーザー操作によって発生します。たとえば、ボタンはユーザーがクリックしたときにクリックイベントをディスパッチし、入力はユーザーが入力値を入力したときに変更イベントをディスパッチします。
これらの自動的にディスパッチされる標準イベントに加えて、Lit 要素はカスタムイベントをディスパッチできます。たとえば、メニュー要素は選択されたアイテムが変更されたことを示すイベントをディスパッチし、ポップアップ要素はポップアップが開いたり閉じたりしたときにイベントをディスパッチする場合があります。
Lit 要素自体を含む任意の Javascript コードは、イベントをリスニングし、イベントに基づいてアクションを実行できます。たとえば、ツールバー要素はメニューアイテムが選択されたときにリストをフィルタリングし、ログイン要素はログインボタンのクリックを処理したときにログインを処理する場合があります。
イベントのリスニング
「イベントのリスニング」へのパーマリンク標準の addEventListener
APIに加えて、Lit はイベントリスナーを追加するための宣言的な方法を導入しています。
要素テンプレートへのイベントリスナーの追加
「要素テンプレートへのイベントリスナーの追加」へのパーマリンクテンプレート内の @
式を使用して、コンポーネントのテンプレート内の要素にイベントリスナーを追加できます。宣言的なイベントリスナーは、テンプレートがレンダリングされるときに追加されます。
イベントリスナーオプションのカスタマイズ
「イベントリスナーオプションのカスタマイズ」へのパーマリンク宣言的なイベントリスナーで使用されるイベントオプション(passive
や capture
など)をカスタマイズする必要がある場合は、@eventOptions
デコレータを使用してリスナーにこれらのオプションを指定できます。@eventOptions
に渡されるオブジェクトは、addEventListener
の options
パラメータとして渡されます。
import {LitElement, html} from 'lit';
import {eventOptions} from 'lit/decorators.js';
//...
@eventOptions({passive: true})
private _handleTouchStart(e) { console.log(e.type) }
デコレータの使用。 デコレータは提案されている JavaScript 機能であるため、デコレータを使用するには、Babel や TypeScript などのコンパイラを使用する必要があります。詳細については、「デコレータの有効化」を参照してください。
デコレータを使用していない場合は、イベントリスナー式にオブジェクトを渡して、イベントリスナーオプションをカスタマイズできます。オブジェクトには handleEvent()
メソッドが含まれている必要があり、addEventListener()
の options
引数に通常表示されるオプションを含めることができます。
render() {
return html`<button @click=${{handleEvent: () => this.onClick(), once: true}}>click</button>`
}
コンポーネントまたはそのシャドウルートへのイベントリスナーの追加
「コンポーネントまたはそのシャドウルートへのイベントリスナーの追加」へのパーマリンクコンポーネントのスロットされた子要素と、コンポーネントテンプレートを介してシャドウDOMにレンダリングされた子要素からディスパッチされたイベントを通知するには、標準のaddEventListener
DOMメソッドを使用してコンポーネント自体にリスナーを追加できます。詳細は、MDN の EventTarget.addEventListener() を参照してください。
コンポーネントコンストラクタは、コンポーネントにイベントリスナーを追加するのに適した場所です。
constructor() {
super();
this.addEventListener('click', (e) => console.log(e.type, e.target.localName));
}
コンポーネント自体にイベントリスナーを追加することは、イベントデリゲーションの一種であり、コードを削減したり、パフォーマンスを向上させたりするために実行できます。詳細は、「イベントデリゲーション」を参照してください。通常、これを行う場合、イベントの target
プロパティを使用して、どの要素がイベントを発生させたかに基づいてアクションを実行します。
ただし、コンポーネントのシャドウDOMから発生したイベントは、コンポーネントのイベントリスナーによって認識されるとリターゲティングされます。つまり、イベントターゲットはコンポーネント自体になります。「Shadow DOM でのイベントの処理」で詳細情報をご覧ください。
リターゲティングはイベントデリゲーションを妨げる可能性があり、これを回避するために、イベントリスナーはコンポーネントのシャドウルート自体に追加できます。shadowRoot
は constructor
では使用できないため、イベントリスナーは次のように createRenderRoot
メソッドに追加できます。createRenderRoot
メソッドからシャドウルートを返すことを確認することが重要です。
他の要素へのイベントリスナーの追加
「他の要素へのイベントリスナーの追加」へのパーマリンクコンポーネントが自身またはそのテンプレート化されたDOM以外のもの(たとえば、Window
、Document
、またはメインDOM内の要素)にイベントリスナーを追加する場合は、connectedCallback
でリスナーを追加し、disconnectedCallback
で削除する必要があります。
disconnectedCallback
でイベントリスナーを削除すると、コンポーネントによって割り当てられたメモリは、コンポーネントが破棄されたり、ページから切断されたりしたときにクリーンアップされます。connectedCallback
(コンストラクタやfirstUpdated
などではなく)でイベントリスナーを追加すると、コンポーネントがDOMから切断され、その後再び接続された場合に、コンポーネントはイベントリスナーを再作成します。
connectedCallback() {
super.connectedCallback();
window.addEventListener('resize', this._handleResize);
}
disconnectedCallback() {
window.removeEventListener('resize', this._handleResize);
super.disconnectedCallback();
}
カスタム要素の使用に関する MDN のドキュメントについては、「ライフサイクルコールバック」を参照してください。
パフォーマンスの最適化
「パフォーマンスの最適化」へのパーマリンクイベントリスナーの追加は非常に高速であり、通常はパフォーマンス上の問題ではありません。ただし、高頻度で使用され、多くのイベントリスナーを必要とするコンポーネントの場合、イベントデリゲーション を使用して使用されるリスナーの数を減らし、レンダリング後に非同期的にリスナーを追加することで、最初のレンダリングのパフォーマンスを最適化できます。
イベントデリゲーション
「イベントデリゲーション」へのパーマリンクイベントデリゲーションを使用すると、使用されるイベントリスナーの数を減らし、パフォーマンスを向上させることができます。コードを削減するために、イベント処理を集中化することも便利です。イベントデリゲーションは、バブルするイベントを処理する場合にのみ使用できます。バブリングの詳細については、「イベントのディスパッチ」を参照してください。
バブリングイベントは、DOM内の任意の祖先要素で認識できます。これを利用して、祖先コンポーネントに単一のイベントリスナーを追加し、DOM内の任意の子孫によってディスパッチされたバブリングイベントを通知できます。イベントの target
プロパティを使用して、イベントをディスパッチした要素に基づいて特定のアクションを実行します。
イベントリスナーの非同期追加
「イベントリスナーの非同期追加」へのパーマリンクレンダリング後にイベントリスナーを追加するには、firstUpdated
メソッドを使用します。これは、コンポーネントが最初に更新されてテンプレート化されたDOMをレンダリングした後に実行されるLitライフサイクルコールバックです。
firstUpdated
コールバックは、コンポーネントが最初に更新され、その render
メソッドが呼び出された後(ただし、ブラウザがペイントする前に)に発生します。
詳細については、ライフサイクルドキュメントのfirstUpdated を参照してください。
リスナーがユーザーがコンポーネントを見ることができるようになった後に追加されるようにするには、ブラウザがペイントされた後に解決される Promise を待機できます。
async firstUpdated() {
// Give the browser a chance to paint
await new Promise((r) => setTimeout(r, 0));
this.addEventListener('click', this._handleClick);
}
イベントリスナーにおける this
の理解
「イベントリスナーにおける this の理解」へのパーマリンク テンプレートで宣言的な @
構文を使用して追加されたイベントリスナーは、自動的にコンポーネントにバインドされます。
したがって、宣言的なイベントハンドラ内で this
を使用して、コンポーネントインスタンスを参照できます。
class MyElement extends LitElement {
render() {
return html`<button @click="${this._handleClick}">click</button>`;
}
_handleClick(e) {
console.log(this.prop);
}
}
addEventListener
を使用してリスナーを命令的に追加する場合、this
がコンポーネントを参照するように、アロー関数を使いましょう。
export class MyElement extends LitElement {
private _handleResize = () => {
// `this` refers to the component
console.log(this.isConnected);
}
constructor() {
window.addEventListener('resize', this._handleResize);
}
}
詳細については、MDN のthis
のドキュメントを参照してください。
繰り返しテンプレートから発生したイベントのリスニング
「繰り返しテンプレートから発生したイベントのリスニング」へのパーマリンク繰り返しアイテムのイベントをリスニングする際には、イベントがバブルする場合はイベントデリゲーション を使用するのが便利な場合があります。イベントがバブルしない場合、繰り返し要素にリスナーを追加できます。両方の方法の例を以下に示します。
イベントリスナーの削除
「イベントリスナーの削除」へのパーマリンク@
式に null
、undefined
、または何も渡すと、既存のリスナーが削除されます。
イベントのディスパッチ
“イベントのディスパッチ”へのパーマリンクすべてのDOMノードは、dispatchEvent
メソッドを使用してイベントをディスパッチできます。 まず、イベントの種類とオプションを指定して、イベントインスタンスを作成します。 次に、それを以下のようにdispatchEvent
に渡します。
const event = new Event('my-event', {bubbles: true, composed: true});
myElement.dispatchEvent(event);
bubbles
オプションを使用すると、イベントをDOMツリーを上って、ディスパッチしている要素の祖先に伝播させることができます。イベントがイベントデリゲーションに参加できるようにするには、このフラグを設定することが重要です。
composed
オプションは、要素が存在するシャドウDOMツリーの上でイベントをディスパッチできるように設定するのに役立ちます。
詳細については、シャドウDOMでのイベントの処理を参照してください。
イベントのディスパッチの完全な説明については、MDNのEventTarget.dispatchEvent()を参照してください。
イベントをディスパッチするタイミング
“イベントをディスパッチするタイミング”へのパーマリンクイベントは、ユーザーの操作またはコンポーネントの状態の非同期的な変更に応じてディスパッチする必要があります。一般的に、コンポーネントのプロパティまたは属性APIを介して所有者によって行われた状態の変化に応じてディスパッチするべきではありません。これは、ネイティブのWebプラットフォーム要素が動作する一般的な方法です。
たとえば、ユーザーがinput
要素に値を入力するとchange
イベントがディスパッチされますが、コードがinput
のvalue
プロパティを設定しても、change
イベントはディスパッチされません。
同様に、メニューコンポーネントは、ユーザーがメニュー項目を選択したときにイベントをディスパッチする必要がありますが、たとえば、メニューのselectedItem
プロパティが設定された場合にイベントをディスパッチするべきではありません。
これは通常、コンポーネントがリスンしている別のイベントに応じてイベントをディスパッチする必要があることを意味します。
要素の更新後のイベントのディスパッチ
“要素の更新後にイベントをディスパッチする”へのパーマリンク多くの場合、イベントは要素が更新されてレンダリングされた後でのみ発生させる必要があります。これは、ユーザー操作に基づいてレンダリングされた状態の変化を伝えることを目的としたイベントの場合に必要になる可能性があります。この場合、状態を変更した後、イベントをディスパッチする前に、コンポーネントのupdateComplete
Promiseをawaitできます。
標準イベントまたはカスタムイベントの使用
“標準イベントまたはカスタムイベントの使用”へのパーマリンクイベントは、Event
またはCustomEvent
を構築することでディスパッチできます。どちらのアプローチも妥当です。CustomEvent
を使用する場合、イベントデータはイベントのdetail
プロパティに渡されます。Event
を使用する場合、イベントのサブクラスを作成し、カスタムAPIをアタッチできます。
イベントの構築の詳細については、MDNのEventを参照してください。
カスタムイベントの発生
“カスタムイベントの発生:”へのパーマリンクconst event = new CustomEvent('my-event', {
detail: {
message: 'Something important happened'
}
});
this.dispatchEvent(event);
詳細については、カスタムイベントに関するMDNのドキュメントを参照してください。
標準イベントの発生
“標準イベントの発生:”へのパーマリンクclass MyEvent extends Event {
constructor(message) {
super();
this.type = 'my-event';
this.message = message;
}
}
const event = new MyEvent('Something important happened');
this.dispatchEvent(event);
Shadow DOM でのイベントの処理
“シャドウDOMでのイベントの処理”へのパーマリンクシャドウDOMを使用する場合、標準的なイベントシステムにいくつかの変更があり、理解することが重要です。シャドウDOMは主に、これらの「シャドウ」要素に関する詳細をカプセル化するDOMでのスコープメカニズムを提供するために存在します。そのため、シャドウDOM内のイベントは、外部のDOM要素から特定の詳細をカプセル化します。
合成イベントディスパッチの理解
“合成イベントディスパッチの理解”へのパーマリンクデフォルトでは、シャドウルート内でディスパッチされたイベントは、そのシャドウルートの外からは見えません。イベントをシャドウDOMの境界を越えて渡すには、composed
プロパティをtrue
に設定する必要があります。DOMツリー内のすべてのノードがイベントを見ることができるように、composed
とbubbles
を組み合わせることが一般的です。
_dispatchMyEvent() {
let myEvent = new CustomEvent('my-event', {
detail: { message: 'my-event happened.' },
bubbles: true,
composed: true });
this.dispatchEvent(myEvent);
}
イベントがcomposed
でbubble
の場合、イベントをディスパッチする要素のすべての祖先(外部のシャドウルート内の祖先を含む)で受信できます。イベントがcomposed
であるがbubble
でない場合、イベントをディスパッチする要素と、シャドウルートを含むホスト要素でのみ受信できます。
すべてのマウス、タッチ、キーボードイベントを含む、ほとんどの標準的なユーザーインターフェースイベントは、バブリングと合成の両方です。合成イベントに関するMDNのドキュメントで詳細を確認してください。
イベントのリターゲティングの理解
“イベントのリターゲティングの理解”へのパーマリンク合成イベントは、シャドウルート内からディスパッチされるとリターゲティングされます。つまり、シャドウルートまたはその祖先のいずれかをホストする要素のリスナーに対しては、ホスト要素から来たように見えます。Litコンポーネントはシャドウルートにレンダリングされるため、Litコンポーネント内からディスパッチされたすべての合成イベントは、Litコンポーネント自体によってディスパッチされたように見えます。イベントのtarget
プロパティはLitコンポーネントです。
<my-element onClick="(e) => console.log(e.target)"></my-element>
render() {
return html`
<button id="mybutton" @click="${(e) => console.log(e.target)}">
click me
</button>`;
}
高度なケースでイベントの起源を特定する必要がある場合は、event.composedPath()
APIを使用します。このメソッドは、シャドウルート内のノードを含む、イベントディスパッチによってトラバースされたすべてのノードの配列を返します。これはカプセル化を破るため、公開される可能性のある実装の詳細に依存しないように注意する必要があります。一般的なユースケースには、クライアントサイドルーティングのために、クリックされた要素がアンカータグかどうかを判断することが含まれます。
handleMyEvent(event) {
console.log('Origin: ', event.composedPath()[0]);
}
詳細については、composedPathに関するMDNのドキュメントを参照してください。
イベントディスパッチャとリスナー間の通信
“イベントディスパッチャとリスナー間の通信”へのパーマリンクイベントは主に、イベントディスパッチャからイベントリスナーへの変更を伝えるために存在しますが、リスナーからディスパッチャへの情報を伝えるためにも使用できます。
これを行う1つの方法は、リスナーがコンポーネントの動作をカスタマイズするために使用できるAPIをイベントに公開することです。たとえば、リスナーはカスタムイベントのdetailプロパティにプロパティを設定できます。それをディスパッチコンポーネントが動作のカスタマイズに使用します。
ディスパッチャとリスナー間の通信を行うもう1つの方法は、preventDefault()
メソッドを使用することです。イベントの標準的なアクションが発生しないようにするために呼び出すことができます。リスナーがpreventDefault()
を呼び出すと、イベントのdefaultPrevented
プロパティがtrueになります。このフラグは、その後、リスナーが動作をカスタマイズするために使用できます。
これらの両方の技術は、次の例で使用されています。