在 Lightning Web 组件中处理事件
学习目标
完成本单元后,您将能够:
- 创建包含多个组件的应用程序。
- 描述复杂组件的文件结构。
- 处理事件。
跟踪事件过程
您已经构建了一个组件并将其推送到组织中。让我们开始为事件处理添加一些交互性。我们通过若干组件跟踪事件的过程,以便在应用程序中处理复杂事件。此应用程序是自行车商店采用的产品选择器。用户通过点击自行车名称和图像来查看更多详细信息。

此应用程序有四个协同工作的组件。
-
tile(磁贴):显示单个项目。
-
list(列表):排列各磁贴。
-
detail(详细信息):点击一个磁贴时,会显示该磁贴的详细信息(与您刚创建的 bikeCard 相似)。
-
selector(选择器):包含整套组件。容器组件并非必需的,但我们在此处使用了一个容器组件,以帮助处理事件。
目前,该应用程序使用数据文件加载静态数据进行测试。在下一单元中,您将学习如何从组织中提取动态数据。
组件构成
让我们在项目中添加一些可以部署到组织的文件。
- 在此处下载此应用程序的文件:Trailhead 的自行车选择器应用程序。
- 将文件解压到 bikeCard 项目的 force-app/main/default/lwc 文件夹中。

组件关系
在此应用程序中,多个组件协同工作;某些组件嵌套在其他组件中。就像 HTML 元素相互嵌套一样,Lightning web 组件(即自定义 HTML 元素)也可以嵌套在其他 Lightning web 组件中。
在我们的文件系统中,组件的文件夹并不能真正深入了解它们之间的关系。
让我们来看看这些组件是如何在 UI 层面上以图表的形式嵌套的。

通过查看文件,您可以看到选择器组件布局了页面,并呈现了列表 (c-list) 和详细信息 (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} 和 lwc:else)。如果未从列表中选择任何内容,则会显示一条要求用户进行选择的消息。如果选择了某个内容,则会显示自行车信息。
列表组件呈现了多个磁贴 (c-tile) 组件,数据中的每款自行车都对应一个组件。这种嵌套是在每个父级组件的 HTML 中实现的。例如,列表组件具有以下 HTML,包括磁贴组件 c-tile。
<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>请注意,自行车项目的每次迭代都会生成一个新的磁贴组件。只要包含 c-tile 组件标签,每个磁贴组件就会成为它的子级组件。Div 类定义“container”(容器)用于设置样式,因此您可以控制磁贴的排列。如果查看 list.css,便能看到它封装了内容。
.container { display: flex; flex-direction: row; flex-wrap: wrap; }父级/子级关系对于应用程序的设计和事件处理都很重要。
让我们更深入地探讨一下事件处理。
事件向上,属性向下
在复杂组件(包含若干父级和子级组件)中,组件可以向上和向下交流。

- 子级组件 c-todo-item 向父级组件 c-todo-app 派发一个事件。例如,当用户点击按钮时,子级组件可以将事件对象传递给父级组件,这样父级组件就可以处理该事件并更改当前页面。
- c-todo-app 父级组件在子级组件中传递属性或调用方法。例如,父级组件可以在子级组件中设置文本值,或调用某个方法。
下面让我们来看看这种交流是如何进行的。
向上传递信息
可以使用 events(事件)和 event listeners(事件监听器)向上传递信息。
子级组件派发事件,父级组件监听事件。派发事件包括创建子级组件可以传递给父级组件的事件对象。父级组件有一个处理程序来响应该事件。
例如(不要创建这些组件),像这样的子级组件包含 nextHandler() 方法,该方法使用 CustomEvent() 创建一个简单的事件对象,并在用户点击 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;
}
}向下传递信息
可以使用 public properties(公共属性)和 public methods(公共方法)向下传递信息。
您可以使用 @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 来执行一些逻辑。记住要用 @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 中处理事件
因此,我们的选择器应用程序需要处理一种类型的事件——用户点击一个磁贴。当这种情况发生时,详细信息组件应该用相关磁贴的信息重新渲染。您可以用 HTML(在模板中添加事件监听器)或 JavaScript(编写事件监听器函数)处理事件。我们推荐使用 HTML 方法,如下所示。
每个磁贴组件都会监听用户的点击情况,因为磁贴组件的 HTML (tile.html) 包含一个 onclick 事件监听器。
<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 中的一个磁贴实例时,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 组件。然后向下传递一个属性给详细信息组件。
在我们的文件中,您可以看到以下内容。
- tile.html 具有调用
tileClick处理程序的onclick事件监听器。
- Tile.js 具有
tileClick方法,可以创建事件类型为tileclick的新CustomEvent和包含detail值 (this.product.fields.Id.value) 的对象。
- list.html 具有调用
handleTileClick处理程序的ontileclick监听器。
- List.js 具有
handleTileClick方法,它传入事件 (evt) 来创建另一个CustomEvent(productselected),带有同样包含detail值evt.detail的对象。同时在 JavaScript 中派发该事件:// Fire the event from c-list this.dispatchEvent(event);
- selector.html 具有调用
handleProductSelected处理程序的onproductselected事件监听器。
- Selector.js 具有
handleProductSelected方法,它将selectedProductId设置为传入的evt.detail值。变量 "selectedProductId" 从选择器组件传递到 selector.htm 中的详细信息组件:product-id={selectedProductId}。
- detail.html 有一个条件指令(还记得第 2 单元中的指令吗?)等待着产品值:
<template lwc:if={product}> - detail.js 将这些部分结合起来。它会创建一个私有变量
_productId来跟踪productId值的状态。然后使用获取/设置模式来获取该值,并将其设置为变量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:打开默认组织),打开您的组织。
- 使用选择器组件创建一个区域页面。
- 添加一个标签
Your Bike Selection(您选择的自行车)。
- 将 selector(选择器)组件拖动到页面布局的顶部。
- 保存并为所有用户激活。
- 打开后可以看到组件已经出现在 UI 中。
至此,您就拥有了一个完全交互式的页面,由若干协同工作的组件组成。接下来,我们将尝试设置样式,并从组织获取实时数据。
资源
- Lightning Web 组件开发人员指南:Shadow DOM
- Lightning Web 组件开发人员指南:事件交流
- Lightning Web 组件开发人员指南:创建 Getter 和 Setter
