Lightning Web コンポーネントでのイベントの処理
学習の目的
この単元を完了すると、次のことができるようになります。
- 複数のコンポーネントを含むアプリケーションを作成する。
- 複雑なコンポーネントのファイル構造を説明する。
- イベントを処理する。
イベントのジャーニーをたどる
コンポーネントを作成して組織に転送しました。ここでは、イベント処理とのインタラクションを追加しましょう。アプリケーションで高度なイベント処理を行うために複数のコンポーネントを進むイベントのジャーニーを確認します。このアプリケーションは、サイクルショップの商品セレクターです。ユーザーは自転車名と画像をクリックして詳細を表示します。
このアプリケーションでは 4 つのコンポーネントが連携しています。
-
tile: 個々の品目を表示します。
-
list: タイルを配置します。
-
detail: タイルがクリックされると品目の詳細を表示します (作成した bikeCard に類似)。
-
selector: すべてのコンポーネントが含まれます。コンテナコンポーネントは必要ありませんが、ここでは、イベント処理をしやすくするためにコンテナを 1 つ使用します。
当面、アプリケーションはデータファイルを使用してテスト用に静的データを読み込みます。次の単元では、組織から動的データを取得する方法を学習します。
コンポーネントのコンポジション
では、いくつかのファイルをプロジェクトに追加して組織にリリースできるようにしましょう。
- このアプリケーションのファイルを Trailhead 用自転車セレクターアプリケーションからダウンロードします。
- ファイルを 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; }
この親/子リレーションは、アプリケーションの設計上だけでなく、イベント処理にとっても重要です。
では、イベント処理についてもう少し掘り下げましょう。
イベントは上へ、プロパティは下へ
複雑なコンポーネント (複数の親コンポーネントと子コンポーネントが含まれるもの) の場合、コンポーネントは階層の上位および下位と通信できます。
- c-todo-item 子コンポーネントは、c-todo-app 親コンポーネントにイベントをディスパッチします。たとえば、ユーザーがボタンをクリックしたら、子が親にイベントオブジェクトを渡し、親がイベントを処理して現在のページを変更できます。
- c-todo-app 親コンポーネントは、プロパティを子コンポーネントに渡す、または子コンポーネント内でメソッドを呼び出します。たとえば、親は子コンポーネントのテキスト値を設定、または子コンポーネント内のメソッドを呼び出すことができます。
この通信のしくみを見てみましょう。
情報を上位に渡す
情報は、イベントまたはイベントリスナーを使用して上位に渡すことができます。
子コンポーネントはイベントをディスパッチし、親コンポーネントはそれをリスンします。イベントのディスパッチには、子が親コンポーネントに渡すことができるイベントオブジェクトの作成が含まれます。親にはイベントに応答するハンドラーがあります。
たとえば (このようなコンポーネントは作成しないでください)、次の子コンポーネントには、CustomEvent()
を使用してシンプルイベントオブジェクトを作成する nextHandler()
メソッドが含まれており、ユーザーが [Next (次へ)] ボタンをクリックすると、「next」というイベント種別をディスパッチします。
// todoItem.js import { LightningElement } from 'lwc'; ... nextHandler() { this.dispatchEvent(new CustomEvent('next')); } }
親コンポーネントは「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 コンポーネントまで渡します。
このファイルでは、次のようになります。
- tile.html には
onclick
イベントリスナーがあり、tileClick
ハンドラーをコールします。
- tile.js には
tileClick
メソッドがあり、イベント種別がtileclick
である新しいCustomEvent
が作成されます。これには、detail
値 (this.product.fields.Id.value
) を含むオブジェクトも設定されます。
- list.html には
ontileclick
リスナーがあり、handleTileClick
ハンドラーをコールします。
- list.js には
handleTileClick
メソッドがあり、event (evt
) を渡して、別のCustomEvent
(productselected
) を作成します。これにも、detail
値evt.detail
を含むオブジェクトが設定されます。また、JavaScript でイベントをディスパッチします。// Fire the event from c-list this.dispatchEvent(event);
- selector.html には
onproductselected
イベントリスナーがあり、handleProductSelected
ハンドラーをコールします。
- selector.js には
handleProductSelected
メソッドがあり、selectedProductId
をそれ自体に渡されたevt.detail
値に設定します。変数 selectedProductId は、selector.html で selector コンポーネントから detail コンポーネントに渡されます。product-id={selectedProductId}
- detail.html には条件付きディレクティブ (単元 2 を思い出してください) があり、product 値
<template lwc:if={product}>
を待機します。 - 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; } }
タイルをクリックするたびに、このプロセスは自己反復します。
組織へのファイルのリリース
では、この新しい bikeCard プロジェクトファイルを組織にリリースして、どう機能するか見てみましょう。前の単元で実行したのと同じ手順を使用して、新しいファイルをリリースし、組織を開き、Lightning アプリケーションビルダーでこのアプリケーションにページを作成します。
- VS Code bikeCard プロジェクトで、force-app/main/default フォルダーを右クリックして、[SFDX: Deploy Source to Org (SFDX: 組織にソースをリリース)] を選択します。
- VS Code のコマンドパレットの [SFDX: Open Default Org (SFDX: デフォルト組織を開く)] を使用して、組織を開きます。
- selector コンポーネントを使用して範囲が 1 つのページを作成します。
-
Your Bike Selection
という表示ラベルを設定します。
-
selector コンポーネントをページレイアウトの最上部にドラッグします。
- 保存して、すべてのユーザーを対象に有効化します。
- ページを開き、コンポーネントが UI で動作することを確認します。
連携する複数のコンポーネントで構成された完全にインタラクティブなページができました。次は、スタイルを設定し、組織からライブデータを取得します。
リソース
- Lightning Web Components Developer Guide (Lightning Web コンポーネント開発者ガイド): Shadow DOM
- Lightning Web Components Developer Guide (Lightning Web コンポーネント開発者ガイド): イベントを使用した通信
- Lightning Web Components Developer Guide (Lightning Web コンポーネント開発者ガイド): Getter と Setter の作成