Skip to main content
Join the Agentforce Hackathon on Nov. 18-19 to compete for a $20,000 Grand Prize. Sign up now. Terms apply.

Lightning Web コンポーネントでのイベントの処理

学習の目的

この単元を完了すると、次のことができるようになります。

  • 複数のコンポーネントを含むアプリケーションを作成する。
  • 複雑なコンポーネントのファイル構造を説明する。
  • イベントを処理する。

イベントのジャーニーをたどる

コンポーネントを作成して組織に転送しました。ここでは、イベント処理とのインタラクションを追加しましょう。アプリケーションで高度なイベント処理を行うために複数のコンポーネントを進むイベントのジャーニーを確認します。このアプリケーションは、サイクルショップの商品セレクターです。ユーザーは自転車名と画像をクリックして詳細を表示します。

自転車セレクターのコンポーネントパーツ。

このアプリケーションでは 4 つのコンポーネントが連携しています。

  1. tile: 個々の品目を表示します。
  2. list: タイルを配置します。
  3. detail: タイルがクリックされると品目の詳細を表示します (作成した bikeCard に類似)。
  4. selector: すべてのコンポーネントが含まれます。コンテナコンポーネントは必要ありませんが、ここでは、イベント処理をしやすくするためにコンテナを 1 つ使用します。

当面、アプリケーションはデータファイルを使用してテスト用に静的データを読み込みます。次の単元では、組織から動的データを取得する方法を学習します。

コンポーネントのコンポジション

では、いくつかのファイルをプロジェクトに追加して組織にリリースできるようにしましょう。

  1. このアプリケーションのファイルを Trailhead 用自転車セレクターアプリケーションからダウンロードします。
  2. ファイルを bikeCard プロジェクトの force-app/main/default/lwc フォルダーに展開します。 自転車セレクターアプリケーションのファイル構造。

コンポーネントのリレーション

このアプリケーションでは、複数のコンポーネントが連携します。他のコンポーネント内にネストされているコンポーネントもあります。HTML 要素が互いにネストできるように、カスタム HTML 要素である Lightning Web コンポーネントは、他の Lightning Web コンポーネント内にネストできます。

ファイルシステムでは、コンポーネントのフォルダーからコンポーネント間のリレーションに関するインサイトは得られません。

UI レベルでコンポーネントがどうネストされるか図で見てみましょう。

自転車セレクターアプリケーションコンポーネントの親/子リレーション。

ファイルを見てみると、selector コンポーネントでページがレイアウトされ、list (c-list) と detail (c-detail) コンポーネントが表示されていることがわかります。

<template>
  <div class="wrapper">
    <header class="header">Select a Bike</header>
    <section class="content">
      <div class="columns">
        <main class="main" >
          <c-list onproductselected={handleProductSelected}></c-list>
        </main>
        <aside class="sidebar-second">
          <c-detail product-id={selectedProductId}></c-detail>
        </aside>
      </div>
    </section>
  </div>
</template>

detail.html を次のように更新します。

<template>
  <template lwc:if={product}>
    <div class="container">
      <div>{product.fields.Name.value}</div>
      <div class="price">{product.fields.MSRP__c.displayValue}</div>
      <div class="description">{product.fields.Description__c.value}</div>
      <img class="product-img" src={product.fields.Picture_URL__c.value} alt={product.fields.Name.value}/>
      <p>
        <lightning-badge label={product.fields.Material__c.value}></lightning-badge>
        <lightning-badge label={product.fields.Level__c.value}></lightning-badge>
      </p>
      <p>
        <lightning-badge label={product.fields.Category__c.value}></lightning-badge>
      </p>
    </div>
  </template>
  <template lwc:else>
    <div>Select a bike</div>
  </template>
</template>

Detail.html には、条件付き表示 (lwc:if={product} and lwc:else) があります。リストから何も選択されなかった場合、ユーザーに選択を促すメッセージが表示されます。選択された場合は、自転車情報が表示されます。

list コンポーネントは、データの自転車ごとに 1 つずつ、複数の tile (c-tile) コンポーネントを表示します。このネストは、各親コンポーネントの HTML で行われます。たとえば、list コンポーネントには、tile コンポーネント (c-tile) を含む、次の HTML があります。

<template>
  <div class="container">
    <template for:each={bikes} for:item="bike">
      <c-tile key={bike.fields.Id.value} product={bike} ontileclick={handleTileClick}></c-tile>
    </template>
  </div>
</template>

自転車品目の反復ごとに新しい tile コンポーネントが発生する方法に注目してください。c-tile コンポーネントタグを含めるだけで、各 tile コンポーネントが子になります。div クラス定義の "container" がスタイルに使用されているため、タイルの配置を制御できます。list.css を見ると、コンテンツがラップされていることがわかります。

.container { display: flex; flex-direction: row; flex-wrap: wrap; }

この親/子リレーションは、アプリケーションの設計上だけでなく、イベント処理にとっても重要です。

では、イベント処理についてもう少し掘り下げましょう。

イベントは上へ、プロパティは下へ

複雑なコンポーネント (複数の親コンポーネントと子コンポーネントが含まれるもの) の場合、コンポーネントは階層の上位および下位と通信できます。

情報を上下に渡す親コンポーネントと子コンポーネント。

  1. c-todo-item 子コンポーネントは、c-todo-app 親コンポーネントにイベントをディスパッチします。たとえば、ユーザーがボタンをクリックしたら、子が親にイベントオブジェクトを渡し、親がイベントを処理して現在のページを変更できます。
  2. c-todo-app 親コンポーネントは、プロパティを子コンポーネントに渡す、または子コンポーネント内でメソッドを呼び出します。たとえば、親は子コンポーネントのテキスト値を設定、または子コンポーネント内のメソッドを呼び出すことができます。

この通信のしくみを見てみましょう。

情報を上位に渡す

情報は、イベントまたはイベントリスナーを使用して上位に渡すことができます。

子コンポーネントはイベントをディスパッチし、親コンポーネントはそれをリスンします。イベントのディスパッチには、子が親コンポーネントに渡すことができるイベントオブジェクトの作成が含まれます。親にはイベントに応答するハンドラーがあります。

たとえば (このようなコンポーネントは作成しないでください)、次の子コンポーネントには、CustomEvent() を使用してシンプルイベントオブジェクトを作成する nextHandler() メソッドが含まれており、ユーザーが [Next (次へ)] ボタンをクリックすると、「next」というイベント種別をディスパッチします。

// todoItem.js
import { LightningElement } from 'lwc';
  ...
  nextHandler() {
    this.dispatchEvent(new CustomEvent('next'));
  }
}
メモ

イベント種別は任意の文字列にすることができますが、DOM イベントの標準に準拠するように大文字とスペースを使用せず、必要に応じてアンダースコアを使用して単語を区切らなければなりません。

親コンポーネントは「on」 (onnext) のプレフィックスが付いたインラインイベントハンドラーでイベントをリスンして、

<!-- todoApp.html -->
<template>
  <c-todo-item onnext={nextHandler}></c-todo-item>
</template>

イベントオブジェクトをイベントハンドラーに渡します。

// todoApp.js
import { LightningElement } from 'lwc';
export default class TodoApp extends LightningElement {
  ...
  nextHandler(){
    this.page = this.page + 1;
  }
}

情報を下位に渡す

情報は、公開プロパティまたは公開メソッドを使用して下位に渡すことができます。

コンポーネントのプロパティは、@api デコレーターを前に付けることで公開できます。それから、外部コンポーネントによる公開プロパティを設定し。

たとえば (これらのコンポーネントは作成しないでください)、c-todo-item 子コンポーネントが次のようになっている場合、

// todoItem.js
import { LightningElement, api } from 'lwc';
export default class TodoItem extends LightningElement {
  @api itemName;
}

親からの値を次のように設定します。

<!-- todoApp.html -->
<template>
  <c-todo-item item-name="Milk"></c-todo-item>
</template>

itemName 変数は、ケバブケース属性 item-name を使用して設定されます。JavaScript 内のプロパティ名はキャメルケースですが、HTML 属性名はケバブケース (ダッシュ区切り) になっており HTML 標準と一致します。マークアップ内の item-name 属性は、itemName JavaScript プロパティに対応付けられます。

公開プロパティは、プリミティブ値、シンプルオブジェクト、配列を下位に渡すのに適したソリューションです。

また、getter と setter を使用して、プロパティが get または set の場合に一部のロジックを実行することもできます。他のコンポーネントに対して公開するために、@api デコレーターでアノテーションを付加することを忘れないでください。

同じように、親コンポーネントからコールできる公開メソッドを作成できます。@api デコレーターを使用して定義し、子コンポーネントに公開メソッドを作成し、それを親コンポーネントからコールします。

次のような子コンポーネントがあるとします (これらのコンポーネントは作成しないでください)。

// videoPlayer.js
import { LightningElement, api } from 'lwc';
export default class VideoPlayer extends LightningElement {
  @api play() {
    // Play music!
  }
}

c-video-player コンポーネントが親コンポーネントに含まれていると、次のように親コンポーネントからメソッドを呼び出すことができます。

// methodCaller.js
import { LightningElement } from 'lwc';
export default class MethodCaller extends LightningElement {
  handlePlay() {
    this.template.querySelector('c-video-player').play();
  }
}

イベントを起動する handlePlay() メソッドを定義しました。次に、querySelector() DOM メソッドを使用して、c-video-player と呼ばれる DOM 要素を検索し、その公開メソッドを呼び出します。

HTML でのイベント処理

このセレクターアプリケーションは、ある種別のイベント、つまりユーザーによるタイルのクリックを処理する必要があります。このイベントが発生すると、detail コンポーネントを関連タイルからの情報と共に再表示する必要があります。イベントは HTML (イベントリスナーをテンプレートに追加) または JavaScript (イベントリスナー関数を記述) で処理できます。次のような HTML アプローチを使用することをお勧めします。

各 tile コンポーネントの HTML (tile.html) には onclick イベントリスナーが含まれているため、tile コンポーネントは、ユーザーのクリックをリスンします。

<template>
  <div class="container">
    <a onclick={tileClick}>
      <div class="title">{product.fields.Name.value}</div>
      <img class="product-img" src={product.fields.Picture_URL__c.value} alt={product.fields.Name.value}/>
    </a>
  </div>
</template>

ユーザーが UI でいずれかの tile インスタンスをクリックすると、onclick リスナーが tile.js JavaScript ファイル内のハンドラー関数 tileClick をコールします。

import { LightningElement, api } from 'lwc';
export default class Tile extends LightningElement {
  @api product; tileClick() {
    const event = new CustomEvent('tileclick', {
      // detail contains only primitives
      detail: this.product.fields.Id.value
    });
    // Fire the event from c-tile
    this.dispatchEvent(event);
  }
}

セレクターアプリケーションのイベントパターン

製品セレクターアプリケーションでは、複雑なコンポーネント (複数の親コンポーネントと子コンポーネントが含まれるもの) を使用します。親コンポーネントが子イベントに応答できるように、イベントをコンポーネント階層の上位に伝搬することを推奨します。他 (イベントを起動したコンポーネント以外) の子コンポーネントがある場合、イベントに応答して、それらの子へとプロパティを下位に渡すことができます。

このパターンは次のようになります。

コンポーネント階層内のイベントフロー。

このためには、イベントリスナーとハンドラーを階層の上位にある ebikes コンポーネントまでチェーニングする必要があります。次にプロパティを下位の detail コンポーネントまで渡します。

このファイルでは、次のようになります。

  1. tile.html には onclick イベントリスナーがあり、tileClick ハンドラーをコールします。
  2. tile.js には tileClick メソッドがあり、イベント種別が tileclick である新しい CustomEvent が作成されます。これには、detail 値 (this.product.fields.Id.value) を含むオブジェクトも設定されます。
  3. list.html には ontileclick リスナーがあり、handleTileClick ハンドラーをコールします。
  4. list.js には handleTileClick メソッドがあり、event (evt) を渡して、別の CustomEvent (productselected) を作成します。これにも、detailevt.detail を含むオブジェクトが設定されます。また、JavaScript でイベントをディスパッチします。
    // Fire the event from c-list
    this.dispatchEvent(event);

  5. selector.html には onproductselected イベントリスナーがあり、handleProductSelected ハンドラーをコールします。
  6. selector.js には handleProductSelected メソッドがあり、selectedProductId をそれ自体に渡された evt.detail 値に設定します。変数 selectedProductId は、selector.html で selector コンポーネントから detail コンポーネントに渡されます。
    product-id={selectedProductId}
  7. detail.html には条件付きディレクティブ (単元 2 を思い出してください) があり、product 値
    <template lwc:if={product}> を待機します。
  8. detail.js はすべてをまとめます。非公開変数 _productId を作成し、productId 値の状態を追跡します。次に、get/set パターンを使用して値を取得し、変数 product に設定すると、detail.html が条件付きコンテンツを読み込みます。

getter と setter はよく使用される JavaScript 構文です。これにより、ロジックと条件をプロパティ割り当てに追加できます。

import { LightningElement, api } from 'lwc';
import { bikes } from 'c/data';
export default class Detail extends LightningElement {
  product;
  // Private var to track @api productId
  _productId = undefined;
  // Use set and get to process the value every time it's
  // requested while switching between products
  set productId(value) {
    this._productId = value;
    this.product = bikes.find(bike => bike.fields.Id.value === value);
  }
  // getter for productId
  @api get productId(){
    return this._productId;
  }
}

タイルをクリックするたびに、このプロセスは自己反復します。

メモ

イベントには、DOM ツリーの上位へのイベントの伝搬を管理するためのプロパティがあります。詳細は、「イベント伝達の設定」を参照してください。デフォルト値の変更は高度なイベント処理向けであり、期待される動作を確認するためのテストが必要です。

組織へのファイルのリリース

では、この新しい bikeCard プロジェクトファイルを組織にリリースして、どう機能するか見てみましょう。前の単元で実行したのと同じ手順を使用して、新しいファイルをリリースし、組織を開き、Lightning アプリケーションビルダーでこのアプリケーションにページを作成します。

  1. VS Code bikeCard プロジェクトで、force-app/main/default フォルダーを右クリックして、[SFDX: Deploy Source to Org (SFDX: 組織にソースをリリース)] を選択します。
  2. VS Code のコマンドパレットの [SFDX: Open Default Org (SFDX: デフォルト組織を開く)] を使用して、組織を開きます。
  3. selector コンポーネントを使用して範囲が 1 つのページを作成します。
  4. Your Bike Selection という表示ラベルを設定します。
  5. selector コンポーネントをページレイアウトの最上部にドラッグします。
  6. 保存して、すべてのユーザーを対象に有効化します。
  7. ページを開き、コンポーネントが UI で動作することを確認します。

連携する複数のコンポーネントで構成された完全にインタラクティブなページができました。次は、スタイルを設定し、組織からライブデータを取得します。

リソース

Salesforce ヘルプで Trailhead のフィードバックを共有してください。

Trailhead についての感想をお聞かせください。[Salesforce ヘルプ] サイトから新しいフィードバックフォームにいつでもアクセスできるようになりました。

詳細はこちら フィードバックの共有に進む