イベント伝達の設定

起動されたイベントは、DOM ツリー内で上方向に伝達できます。どこでイベントが処理されるかを理解するには、イベントがどのように伝達するかを理解してください。

イベントは DOM によって上に伝達されます。これが親子の通信方法で、プロパティは下方向、イベントは上方向に伝達されます。イベントは伝達すると、コンポーネントの API の一部となり、イベントのパス沿いのすべてのコンシューマがイベントを理解する必要があります。伝達のしくみを理解して、目的のコンポーネントで機能する伝達設定の中で最も制限の厳しいものを選択できるようにすることが重要です。

Lightning Web コンポーネントイベントは、DOM イベントと同じルールに従って伝達されます。Lightning Web コンポーネントでは伝達フェーズのみが使用されます。捕捉フェーズへのイベントのディスパッチやリスナーの追加はサポートされていません。イベントのパスは、単純に自分のコンポーネントから始まって、親、そしてその親へと移動するものと考えることができます。

イベントの対象は、コンポーネントインスタンスのシャドウルートを超えては伝達されません。コンポーネントの外部からは、すべてのイベント対象はコンポーネント自体です。一方、シャドウツリーの内部では、ツリー内の特定の対象からのイベントを処理できます。イベントのリスナーを接続する場所とイベントが発生する場所に応じて、異なる対象を設定できます。

このコンテンツは、Salesforce 開発者ブログの記事「How Events Bubble in Lightning Web Components (Lightning Web コンポーネントでのイベントバブルの仕組み)」から引用しています。

Note

イベントを作成するときには、イベントで bubblescomposed の 2 つのプロパティを使用してイベント伝達動作を定義してください。

  • Event.bubbles

    イベントが DOM ツリーを上に伝達するかどうかを示す Boolean 値。デフォルト設定は false です。

  • Event.composed

    イベントがシャドウ境界を越えることができるかどうかを示す Boolean 値。デフォルト設定は false です。

イベントに関する情報を取得するには、Event Web API の次のプロパティとメソッドを使用します。

  • Event.target

    イベントをディスパッチした要素。

    各コンポーネントの内部 DOM は、Shadow DOM でカプセル化されます。シャドウ境界は通常の DOM (Light DOM とも呼ばれる) と Shadow DOM との間の線です。イベントが上に伝達してシャドウ境界を横断した場合は、Event.target の値がリスナーの範囲に応じた要素を表すように変更されます。イベントの対象が変更されても、コンポーネントのカプセル化が維持され、コンポーネントの内部情報が流出するのを防ぎます。

    たとえば、<my-button> に対するクリックリスナーは、クリックが button 要素に対して発生した場合でも、常に my-button を対象として受け取ります。

    1<!-- myButton.html -->
    2<template>
    3  <button>{label}</button>
    4</template>
  • Event.currentTarget

    イベントが DOM をトラバースするときに、このプロパティは常にイベントハンドラが接続されていた要素を参照します。

  • Event.composedPath()

    イベントが DOM をトラバースするときに、リスナーが呼び出されるイベント対象の配列。

静的構成 

静的構成では、スロットが使用されません。次の簡単な例では、c-appc-parent を構成し、これが c-child を構成します。

1<c-app onbuttonclick={handleButtonClick}></c-app>

アプリの親コンポーネントは、ボタンのクリックを処理します。

1<!-- app.html -->
2<template>
3  <h2>My app</h2>
4  <c-parent onbuttonclick={handleButtonClick}></c-parent>
5</template>

親コンポーネントには、子コンポーネントを含むラッパーがあり、両方でボタンのクリックイベントをリスンしています。

1<!-- parent.html -->
2<template>
3  <h3>I'm a parent component</h3>
4  <div class="wrapper" onbuttonclick={handleButtonClick}>
5    <c-child onbuttonclick={handleButtonClick}></c-child>
6  </div>
7</template>

子コンポーネントには、onclick ハンドラを含むボタンがあります。

1<!-- child.html -->
2<template>
3  <h3>I'm a child component</h3>
4  <button onclick={handleClick}>click me</button>
5</template>
1// child.js
2handleClick() {
3    const buttonclicked = new CustomEvent('buttonclick', {
4        //event options
5    });
6    this.dispatchEvent(buttonclicked);
7}

この例では、ボタンがクリックされたとき c-child から buttonclick イベントを発生します。イベントリスナーは、次の要素のカスタムイベントに接続されます。

  • body
  • c-app ホスト
  • c-parent
  • div.wrapper
  • c-child ホスト

フラット化されたツリーは次のようになります。

1<body>
2  <!-- Listening for buttonclick event -->
3  <c-app>
4    <!-- Listening for buttonclick event -->
5    #shadow-root
6    |  <h2>My app</h2>
7    |  <c-parent>
8    |    <!-- Listening for buttonclick event -->
9    |    #shadow-root
10    |    |  <h3>I'm a parent component</h3>
11    |    |  <div class="wrapper">
12    |    |     <!-- Listening for buttonclick event -->
13    |    |     <c-child>
14    |    |      #shadow-root 
15    |    |      |  <!-- Listening for buttonclick event -->
16    |    |      |  <h3>I'm a child component</h3>
17    |    |      |  <button>click me</button>
18    |    |      </c-child>
19    |    |    </div>
20    |  </c-parent>
21  </c-app>
22</body>

bubbles: false と composed: false 

デフォルト設定。イベントは DOM ツリーを上に伝達せず、シャドウ境界を越えることもありません。このイベントをリスンする唯一の方法は、イベントをディスパッチするコンポーネントに直接イベントリスナーを追加することです。

この設定では混乱が最小限に抑えられ、コンポーネントのカプセル化が最良になるため、お勧めします。

イベントが c-child のみまで上に伝達されます。

1<body>
2  <c-app>
3    #shadow-root
4    |  <c-parent>
5    |    #shadow-root
6    |    |  <div class="wrapper">
7    |    |    <c-child>
8    |    |    <!-- Event bubbles up here -->
9    |    |      #shadow-root
10    |    |      |  <h3>I'm a child component</h3>
11    |    |      |  <button>click me</button>
12    |    |     </c-child>
13    |    |  </div>
14    |  </c-parent>
15  </c-app>
16</body>

c-child ハンドラを検査すると、イベントで次の値が返されます。

  • event.currentTarget = c-child
  • event.target = c-child

ここから、以降のいくつかのセクションでは、構成の制約が少ない実装を開始できます。

lwc-recipes リポジトリの c-event-with-data コンポーネントは、bubbles: falsecomposed: false の設定でイベントを作成する c-contact-list-item コンポーネントを消費します。

Tip

bubbles: true と composed: false 

イベントは DOM ツリーを上に伝達しますが、シャドウ境界は越えません。その結果、c-childdiv.wrapper の両方がイベントに反応する可能性があります。

1<body>
2  <c-app>
3    #shadow-root
4    |  <c-parent>
5    |    #shadow-root
6    |    |  <div class="wrapper">
7    |    |  <!-- Event bubbles up here -->
8    |    |    <c-child>
9    |    |    <!-- Event bubbles up here -->
10    |    |      #shadow-root
11    |    |      |  <h3>I'm a child component</h3>
12    |    |      |  <button>click me</button>
13    |    |    </c-child>
14    |    |  </div>
15    |  </c-parent>
16  </c-app>
17</body>

イベントハンドラは次の値を返します。

c-child ハンドラ

  • event.currentTarget = c-child
  • event.target = c-child

div.childWrapper ハンドラ

  • event.currentTarget = div.childWrapper
  • event.target = c-child

この設定には 2 つの使用事例があります。

  • 内部イベントの作成

    コンポーネントのテンプレート内でイベントを上に伝達させるには、テンプレート内の要素でイベントをディスパッチします。イベントは、テンプレート内のみで要素の上位コンポーネントまで上に伝達します。イベントは、シャドウ境界に達すると上への伝達を停止します。

    1// myComponent.js
    2this.template.querySelector("div").dispatchEvent(new CustomEvent("notify", { bubbles: true }));

    イベントは myComponent.js で処理する必要があります。イベントはシャドウ境界を越えないため、コンテナコンポーネントのハンドラは実行されません。

    1<!-- container.html -->
    2<template>
    3  <!-- handleNotify doesn’t execute -->
    4  <c-my-component onnotify={handleNotify}></c-my-component>
    5</template>
  • コンポーネントの祖父母へのイベントの送信

    コンポーネントがスロットに渡され、イベントをそのコンポーネントからコンポーネントを含むテンプレートまでイベントを伝達させる必要がある場合には、ホスト要素でイベントをディスパッチします。イベントは、コンポーネントを含むテンプレートでのみ表示可能です。

    lwc-recipes リポジトリの eventBubbling コンポーネントを要約したサンプルコードを見てみましょう。子から祖父母へのコンポーネント階層は c-contact-list-item-bubbling -> lightning-layout-item -> c-event-bubbling です。

    c-contact-list-item-bubbling コンポーネントは、bubbles: true のカスタムイベント contactselect をディスパッチします。

    イベントリスナー oncontactselect は親 lightning-layout-item 上にあり、イベントは祖父母 c-event-bubbling で処理されます。

    1<!-- eventBubbling.html -->
    2<template>
    3  <lightning-card title="EventBubbling" icon-name="standard:logging">
    4    <template lwc:if={contacts.data}>
    5      <lightning-layout class="slds-var-m-around_medium">
    6        <!-- c-contact-list-item-bubbling emits a bubbling event so a single listener on a containing element works -->
    7        <lightning-layout-item class="wide" oncontactselect={handleContactSelect}>
    8          <template for:each={contacts.data} for:item="contact">
    9            <c-contact-list-item-bubbling
    10              class="slds-show slds-is-relative"
    11              key={contact.Id}
    12              contact={contact}
    13            ></c-contact-list-item-bubbling>
    14          </template>
    15        </lightning-layout-item>
    16      </lightning-layout>
    17    </template>
    18  </lightning-card>
    19</template>
    1// contactListItemBubbling.js
    2import { LightningElement, api } from "lwc";
    3
    4export default class ContactListItemBubbling extends LightningElement {
    5  @api contact;
    6
    7  handleSelect(event) {
    8    // Prevent default behavior of anchor tag click which is to navigate to the href url
    9    event.preventDefault();
    10    const selectEvent = new CustomEvent("contactselect", {
    11      bubbles: true,
    12    });
    13    this.dispatchEvent(selectEvent);
    14  }
    15}

bubbles: true と composed: true 

イベントは DOM ツリーを上に伝達し、シャドウ境界を越えて、ドキュメントのルートまで上に伝達します。

イベントをこのように設定すると、イベント種別はコンポーネントの公開 API の一部となります。また、コンシューマであるコンポーネントとそのすべての上位コンポーネントの API の一部としてイベントが強制的に含まれます。

Important

この設定では、イベントがドキュメントのルートまで上に伝達するため、名前の競合が発生することがあります。名前の競合により、正しくないイベントリスナーが起動することがあります。

1<body>
2<!-- Event bubbles up here -->
3  <c-app>
4  <!-- Event bubbles up here -->
5    #shadow-root
6    |  <c-parent>
7    |  <!-- Event bubbles up here -->
8    |    #shadow-root
9    |    |  <div class="wrapper">
10    |    |  <!-- Event bubbles up here -->
11    |    |    <c-child>
12    |    |    <!-- Event bubbles up here -->
13    |    |      #shadow-root
14    |    |      |  <h3>I'm a child component</h3>
15    |    |      |  <button>click me</button>
16    |    |   </c-child>
17    |    |  </div>
18    |  </c-parent>
19  </c-app>
20</body>

この設定を使用する場合は、mydomain__myevent のように名前空間をプレフィックスとしてイベント種別に付加します。HTML イベントリスナー名は onmydomain__myevent のようなおかしな名前になります。

bubbles: false と composed: true 

Lightning Web コンポーネントはこの設定を使用しません。

関連トピック

The Japanese Summer '24 guide is now live

日本語の Summer '24 ガイドが公開されました! 「Component Reference (コンポーネントリファレンス)」は、以前と同様にコンポーネントライブラリにあります。