Lightning 웹 구성 요소에서 이벤트 처리하기
학습 목표
이 유닛을 완료하면 다음을 수행할 수 있습니다.
- 여러 구성 요소가 포함된 앱을 만듭니다.
- 복잡한 구성 요소의 파일 구조를 설명합니다.
- 이벤트를 처리합니다.
이벤트 여정 따라가기
여러분은 구성 요소를 만들어 조직에 푸시했습니다. 그럼 이제 이벤트 처리를 사용해 상호작용을 추가해 보겠습니다. 애플리케이션에서 정교한 이벤트를 처리할 수 있도록 여러 구성 요소로 된 이벤트의 여정을 따라갑니다. 이 애플리케이션은 자전거 매장용 제품 선택기입니다. 사용자가 자전거 이름과 이미지를 클릭하면 자세한 내용이 표시됩니다.
이 앱은 네 가지 구성 요소가 함께 작동합니다.
-
tile(타일): 개별 항목을 표시합니다.
-
list(목록): 타일을 정렬합니다.
-
detail(세부 정보): 타일을 클릭하면 항목 세부 정보가 표시됩니다(방금 만든 bikeCard와 유사함).
-
selector(선택기): 전체 구성 요소 집합이 포함됩니다. 컨테이너 구성 요소가 반드시 필요한 것은 아니지만, 여기서는 이벤트 처리를 돕기 위해 컨테이너 구성 요소를 사용하고 있습니다.
현재 애플리케이션은 데이터 파일을 사용하여 테스트를 위한 정적 데이터를 로드합니다. 다음 유닛에서는 조직에서 동적 데이터를 가져오는 방법을 알아보겠습니다.
구성 요소 구성
프로젝트에 조직에 배포할 수 있는 몇 가지 파일을 추가해 보겠습니다.
- 다음에서 이 앱의 파일을 다운로드하세요. Trailhead용 Bike Selector 앱
- bikeCard 프로젝트의 force-app/main/default/lwc 폴더의 파일 압축을 풉니다.
구성 요소 관계
이 앱에서는 여러 구성 요소가 함께 작동하며, 일부 구성 요소는 다른 구성 요소 안에 중첩되어 있습니다. HTML 요소를 서로 중첩하는 것과 마찬가지로 사용자 정의 HTML 요소인 Lightning 웹 구성 요소도 다른 Lightning 웹 구성 요소 안에 중첩되도록 할 수 있습니다.
파일 시스템에서 구성 요소의 폴더는 구성 요소 간의 관계에 대한 인사이트를 제공하지 않습니다.
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 상위 구성 요소는 속성을 전달하거나 하위 구성 요소에서 메서드를 호출합니다. 예를 들어, 상위 구성 요소는 하위 구성 요소에서 텍스트 값을 설정하거나 메서드를 호출할 수 있습니다.
이러한 통신이 어떻게 이루어지는지 살펴보겠습니다.
위로 정보 전달하기
이벤트 및 이벤트 리스너를 사용하여 정보를 전달할 수 있습니다.
하위 구성 요소는 이벤트를 전송하고 상위 구성 요소는 이벤트를 수신합니다. 이벤트 디스패치에는 하위 구성 요소가 상위 구성 요소에 전달할 수 있는 이벤트 객체를 생성하는 작업이 포함됩니다. 상위 구성 요소에는 이벤트에 응답하는 핸들러가 있습니다.
예를 들어, (이 구성 요소를 만들지 마세요.) 이와 같은 하위 구성 요소에는 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; } }
아래로 정보 전달하기
공개 속성 및 공개 메서드를 사용하여 정보를 전달할 수 있습니다.
구성 요소 속성 앞에 @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
변수는 kebab case 속성을 사용해 item-name
으로 설정된다는 점에 유의하세요. JavaScript의 속성 이름은 camel case인 반면, HTML 속성 이름은 HTML 표준에 맞게 kebab case(대시로 구분)로 표기합니다. 마크업의 item-name
특성은 JavaScript 속성 itemName
으로 매핑됩니다.
공개 속성은 원시 값, 간단한 객체 및 배열을 전달할 수 있는 훌륭한 솔루션입니다.
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에서 이벤트 처리하기
따라서 selector 앱은 한 가지 이벤트 유형, 즉 사용자가 클릭하는 타일에 해당하는 이벤트를 처리해야 합니다. 이 경우 세부 정보 구성 요소는 관련 타일의 정보를 사용하여 다시 렌더링해야 합니다. 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); } }
Selector 앱 이벤트 패턴
Product selector 앱에서는 복합 구성 요소(여러 개의 상위 및 하위 구성 요소를 포함하는 구성 요소)를 사용합니다. 상위 구성 요소가 하위 이벤트에 응답할 수 있도록 구성 요소 계층 구조를 통해 이벤트를 전파하는 것이 좋습니다. 이벤트를 실행하는 구성 요소가 아닌 다른 하위 구성 요소가 있는 경우 이벤트에 대한 응답으로 해당 하위 구성 요소에게 속성을 전달할 수 있습니다.
패턴은 다음과 같습니다.
이를 위해서는 이벤트 리스너와 핸들러를 계층 구조에서 ebikes 구성 요소로 연결해야 합니다. 그런 다음 속성을 세부 정보 구성 요소로 전달합니다.
파일에서 다음을 확인할 수 있습니다.
- tile.html에는
tileClick
핸들러를 호출하는onclick
이벤트 리스너가 있습니다.
- tile.js에는
tileclick
이벤트 유형 및detail
값(this.product.fields.Id.value
)을 포함하는 객체를 사용하여 새로운CustomEvent
를 생성하는tileClick
메서드가 있습니다.
- list.html에는
handleTileClick
핸들러를 호출하는ontileclick
리스너가 있습니다.
- list.js에는 이벤트(
evt
)를 전달하고detail
값인evt.detail
을 포함하는 객체를 사용하여 또 다른
(productselected)를 생성하는CustomEvent
handleTileClick
메서드가 있습니다. 그리고 다음과 같이 JavaScript에서 이벤트를 전송합니다.// Fire the event from c-list this.dispatchEvent(event);
- selector.html에는
handleProductSelected
핸들러를 호출하는onproductselected
이벤트 리스너가 있습니다.
- selector.js에는
selectedProductId
를 전달된evt.detail
값으로 설정하는handleProductSelected
메서드가 있습니다. 선택기 구성 요소에서 selector.htm의 세부 정보 구성 요소로 'selectedProductId' 변수가 전달됩니다.product-id={selectedProductId}
.
- detail.html에는 제품 값을 기다리는 다음과 같은 조건부 지시문(유닛 2 참조)이 있습니다.
<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/daultSource to Org 폴더를 마우스 오른쪽 버튼으로 클릭하고 SFDX: Deploy Source to Org(SFDX: 조직에 원본 배포)를 선택합니다.
- VS Code의 Command Palette(명령 팔레트)에서 SFDX: Open Default Org(SFDX: 기본 조직 열기)를 사용해 조직을 엽니다.
- 선택기 구성 요소를 사용하여 하나의 영역 페이지를 만듭니다.
- 레이블을
Your Bike Selection
(자전거 선택)으로 지정합니다.
-
Selector(선택기) 구성 요소를 페이지 레이아웃의 상단으로 드래그합니다.
- 모든 사용자가 사용할 수 있도록 저장하고 활성화합니다.
- 열어서 UI에서 작동 중인 구성 요소를 확인합니다.
여러 구성 요소가 함께 작동하는 완전한 상호작용 페이지가 생겼습니다. 다음 유닛에서는 스타일링과 조직에서 라이브 데이터 가져오기를 실험해 보겠습니다.
리소스
- Lightning 웹 구성 요소 개발자 가이드: Shadow DOM
- Lightning 웹 구성 요소 개발자 가이드: 이벤트로 통신하기
- Lightning 웹 구성 요소 개발자 가이드: Getter 및 Setter 만들기