Communication between Lightning Web Components

Усім привіт! Це вже наша третя стаття з серії на тему LWC. У попередніх двох ми розбирали принципи побудови та використання компонентів та базові принципи розмітки і її особливості у LWC. А у цій статті ми розглянемо комунікацію між LWC компонентами, отже давайте починати.

Як розробнику веб-компонентів Lightning (LWC), вам може знадобитися передавати дані між компонентами, як у випадках коли компоненти є батьківськими або ж нащадками інших компонентів, так і навіть не пов’язаними один з одним компонентами. У цій статті ми розглянемо три способи передачі даних між LWC:

  • за допомогою анотації «@api»;
  • за допомогою dispatch event;
  • за допомогою служби повідомлень Lightning message chanel.

1.Для початку, давайте подивимося на анотацію “@api. Ця анотація використовується для визначення загальнодоступних властивостей і методів у компоненті, до яких можуть отримати доступ інші компоненти. Це особливо корисно, коли ми хочемо передати дані між батьківським і дочірнім компонентами. Ми можемо визначити загальнодоступну властивість у дочірньому компоненті за допомогою анотації «@api» та встановити його значення в батьківському компоненті. Наприклад, ми можемо визначити публічну властивість у дочірньому компоненті наступним чином:

import { LightningElement, api } from 'lwc';

export default class ChildComponent extends LightningElement {
    @api message;
}

У батьківському компоненті ми можемо встановити значення властивості ‘message’ для дочірнього компонента таким чином:

<template>
    <c-child-component message="Hello World"></c-child-component>
</template>

У цьому прикладі ми передаємо значення «Hello World» від батьківського компонента до дочірнього компонента за допомогою анотації «@api»

Також розгляньмо використання @api з функціями. Вони дозволяють вам викликати певну функціональність компонента ззовні, з інших компонентів або шаблонів. Це дуже потужний інструмент для спілкування між компонентами та обміну даними та діями.

Ви можете оголосити метод у вашому LWC компоненті та позначити його анотацією @api. Це дозволяє методу стати публічним та доступним для виклику з інших компонентів.

import { LightningElement, api } from 'lwc';

export default class MyComponent extends LightningElement {

    @api
    myMethod(param) {
        console.log('Received parameter:', param);
    }
}

У вашому іншому LWC компоненті ви можете звернутися до компонента, який містить метод з анотацією @api, і викликати цей метод.

<template>
    <c-my-component></c-my-component>
    <lightning-button label="Call Method" onclick={callMethod}></lightning-button>
</template>
import { LightningElement } from 'lwc';

export default class AnotherComponent extends LightningElement {

    callMethod() {
        const myComponent = this.template.querySelector('c-my-component');
        if (myComponent) {
            myComponent.myMethod('Hello from AnotherComponent');
        }
    }
}

В наведеному вище прикладі, коли кнопка буде натиснута на батьківському компоненті – дочірній отримує параметром певний текст та виводить його у консоль.

Використання @api дозволяє публічно експортувати функціональність вашого компонента та забезпечити взаємодію з іншими компонентами. Пам’ятайте, що дотримання практик безпеки та надійного проєктування є важливим для ефективної комунікації між компонентами.

2.Іншим способом комунікації між компонентами є dispatch event, які використовуються для передачі даних між компонентами, які не обов’язково пов’язані. Їх можна використовувати для обміну даними між батьківським і дочірнім компонентами, однорідними компонентами або навіть компонентами, які не належать до однієї ієрархії.

Розглянемо приклад простої форми, де користувач вводить якісь дані та натискає на кнопку «Надіслати».

Ми можемо створити спеціальну подію в компоненті форми, яка запускається, коли користувач натискає кнопку «Надіслати». Подія може містити дані, введені користувачем, які потім можуть бути передані іншому компоненту для подальшої обробки. Ось як ми можемо створити спеціальну подію в компоненті форми:

import { LightningElement } from 'lwc';

export default class FormComponent extends LightningElement {
    handleSubmit() {
        const data = {
            name: this.template.querySelector('input[name="name"]').value,
            email: this.template.querySelector('input[name="email"]').value
        };
        
        const event = new CustomEvent('submitform', {
            detail: data
        });
        
        this.dispatchEvent(event);
    }
}

У цьому прикладі ми створюємо спеціальну подію під назвою «submitform» і передаємо їй об’єкт, що містить дані, введені користувачем. Потім ми використовуємо метод dispatchEvent для запуску івенту.

В іншому компоненті ми можемо прослухати подію «submitform» і обробити отримані дані. Ось як ми можемо прослухати подію та обробити дані:

import { LightningElement } from 'lwc';

export default class ResultComponent extends LightningElement {
    connectedCallback() {
        this.addEventListener('submitform', this.handleFormSubmit.bind(this));
    }

    handleFormSubmit(event) {
        const data = event.detail;
        console.log(`Name: ${data.name}, Email: ${data.email}`);
    }
}

У цьому прикладі ми використовуємо метод addEventListener для прослуховування івенту submitform. Коли подія запускається, викликається метод ‘handleFormSubmit’, і ми витягуємо дані, передані в івенту, за допомогою властивості ‘detail’. Цей спосіб є корисним, коли компоненти не належать до однієї ієрархії.

Іншим способом обробки івенту є використання директиви on у файлі HTML, де викликається дочірній компонент.

Отримання цієї події в батьківському компоненті за допомогою директиви on ви можете зробити наступне:

У HTML-файл батьківського компонента додайте дочірній компонент і включіть директиву on:

<template> 
     <div>
          <c-my-child-component onsubmitform={handleFormSubmit}></c-my-child-component>
     </div>
</template>

У директиві on вкажіть назву події (у цьому випадку onsubmitform) і назву функції, яка оброблятиме подію (у цьому випадку handleFormSubmit).

У файлі JavaScript батьківського компонента визначте функцію handleFormSubmit:

handleFormSubmit(event) {
    const data = event.detail;
    console.log(`Name: ${data.name}, Email: ${data.email}`);
}

У функції handleFormSubmit ви можете отримати доступ до властивостей івенту та виконати будь-які необхідні дії на основі даних у івенті.

Зауважте, що коли ви використовуєте директиву on, функція обробки подій має бути визначена у файлі JavaScript батьківського компонента.

Однією з переваг використання директиви on є те, що вона дозволяє обробляти події більш декларативним способом. Включивши директиву on у файл HTML, ви можете чітко бачити, які події обробляються та які функції їх обробляють, що полегшує розуміння та підтримку коду.

Однак зауважте, що використання директиви on також може зробити код менш гнучким. Якщо вам потрібно обробити ту саму подію в кількох компонентах, вам потрібно буде визначити функцію обробки подій у файлі JavaScript кожного компонента. І також навпаки, якщо ви використовуєте прослуховувач подій у файлі JavaScript батьківського компонента, ви можете визначити функцію обробки подій один раз і повторно використовувати її в кількох компонентах.

Загалом обидва підходи мають свої переваги та недоліки, і вибір між ними залежить від конкретних вимог вашого проекту.

Також давайте розберемо використання параметрів bubbles і composed в наших dispatchEvent.

Bubbles:
Параметр bubbles використовується під час відправлення подій у веб-компонентах Lightning (LWC), щоб контролювати, чи має подія «виходити» через ієрархію DOM. Спливання подій — це механізм, за якого після обробки події в певному елементі вона потім поширюється на батьківські елементи для можливої ​​подальшої обробки.

У LWC події зазвичай випливають за замовчуванням, подібно до стандартних подій браузера. Це означає, що якщо ви відправляєте подію від дочірнього компонента, вона пройде вгору через ієрархію батьківських компонентів, дозволяючи компонентам вищого рівня прослуховувати подію та відповідати на неї, якщо вони вирішать це зробити. Це природне розповсюдження подій може спростити зв’язок між компонентами, дозволяючи батьківському компоненту реагувати на дії, вжиті його дочірніми компонентами.

Composed:
Параметр composed — це ще один атрибут, який використовується під час відправлення подій у LWC, і він визначає, чи може подія перетнути межу тіньової DOM. Тіньовий DOM — це фундаментальна концепція веб-компонентів, яка інкапсулює стилі, структуру та функціональність компонента.

Якщо для параметра composed встановлено значення true під час відправлення події, то ця подія може залишати межі тіньової DOM і оброблятися елементами за її межами. Це може бути корисним, якщо ви хочете спілкуватися між компонентами, які не пов’язані безпосередньо через ієрархію компонентів. Однак налаштування параметра composed як true також може призвести до обробки подій у неочікуваних місцях, якщо не використовувати його обережно.

З іншого боку, якщо для composed встановлено значення false, подія залишається в межах тіньового DOM. Це може забезпечити кращу ізоляцію та контроль над обробкою подій, обмежуючи взаємодію між компонентами та запобігаючи обробці подій за межами призначеної області.

Ці параметри — bubbles та composed — забезпечують гнучкість у тому, як події поширюються та обробляються у веб-компонентах Lightning. Розуміння їхньої поведінки може допомогти вам розробити ефективніші та результативні стратегії зв’язку між вашими компонентами, просуваючи модульний та підтримуваний код. Більш детально про їхню поведінку та рекомендації від Salesforce пропонуємо прочитати в офіційній документації від Salesofrce.

3.Також для комунікації між компонентами існує служба повідомлень Lightning (LMS) — це архітектура обміну повідомленнями з публікацією та підпискою, яка дозволяє нам спілкуватися між непов’язаними компонентами. LMS використовує «канал повідомлень» для визначення зв’язку між компонентами. Компоненти можуть публікувати повідомлення на певному каналі повідомлень, а інші компоненти можуть підписуватися на цей канал, щоб отримати повідомлення.

Розглянемо приклад програми кошика для покупок, де користувач може додавати товари до свого кошика з кількох різних компонентів. Ми можемо використовувати LMS для зв’язку між компонентами та оновлення кошика кожного разу, коли додається або видаляється товар.
Ось як ми можемо використовувати LMS для досягнення цього:

По-перше, нам потрібно створити канал повідомлень, який буде використовуватися для зв’язку між компонентами. Ми можемо створити канал повідомлень за допомогою модуля «messageChannel» у LWC.

У VisualStudio code переходимо у messageChannels створюємо новий файл, називаємо його відповідною та зрозумілою назвою і додаємо у кінці .messageChannel-meta.xml

У сам файл покладемо наступний XML.

<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
    <masterLabel>cartChannel</masterLabel>
    <isExposed>true</isExposed>
    <description>Message Channel for cart updates</description>
    <lightningMessageFields>
        <name>cartItems</name>
        <description>Items in the cart</description>
        <type>Text</type>
    </lightningMessageFields>
</LightningMessageChannel>

Далі нам потрібно створити компонент видавця, який публікуватиме повідомлення на ‘cartChannel’. Припустімо, у нас є компонент списку продуктів, де користувач може додавати товари до свого кошика. Ми можемо створити спеціальну подію в компоненті списку продуктів, яка запускається, коли користувач додає товар у свій кошик. Подія може містити деталі елемента, доданого користувачем, які потім можуть бути передані компоненту видавця.

import { LightningElement, wire } from 'lwc';
import { publish, MessageContext } from 'lightning/messageService';
import CART_CHANNEL from '@salesforce/messageChannel/cartChannel__c';

export default class ProductListComponent extends LightningElement {
    @wire(MessageContext) messageContext;

    handleAddToCart(event) {
        const data = {
            name: event.detail.name,
            price: event.detail.price
        };

        const message = {
            data: data
        };

        publish(this.messageContext, CART_CHANNEL, message);
    }
}

У цьому прикладі ми використовуємо метод «publish», щоб опублікувати повідомлення в «cartChannel». Ми передаємо контекст повідомлення, канал повідомлення та повідомлення як параметри методу «publish».

Нарешті, нам потрібно створити компонент підписки, який буде прослуховувати повідомлення на ‘cartChannel’ і відповідним чином оновлюватиме кошик.

import { LightningElement, wire } from 'lwc';
import { subscribe, MessageContext, unsubscribe } from 'lightning/messageService';
import CART_CHANNEL from '@salesforce/messageChannel/cartChannel__c';

export default class CartComponent extends LightningElement {
    @wire(MessageContext) messageContext;
    subscription;

    connectedCallback() {
        // Subscribe to the CART_CHANNEL
        this.subscription = subscribe(
            this.messageContext,
            CART_CHANNEL,
            (message) => {
                this.handleMessage(message);
            }
        );
    }

    handleMessage(message) {
        // Extract and handle data from the message
        const data = message.data;
        // Update the cart with the data received from the message
    }

    disconnectedCallback() {
        // Unsubscribe from the message channel when the component is disconnected
        unsubscribe(this.subscription);
    }
}

У цьому прикладі ми використовуємо метод «subscribe», щоб підписатися на «cartChannel» і прослуховувати повідомлення. Коли повідомлення отримано, викликається метод ‘handleMessage’, і ми витягуємо дані з повідомлення за допомогою властивості ‘data’.

Це воно! Ми використовували LMS для зв’язку між непов’язаними компонентами та оновлення кошика кожного разу, коли додається або видаляється товар. Надалі ми можемо створювати більше компонентів які будуть публікувати повідомлення у канал з інформацією про додавання продуктів у кошик і користуватись тим же каналом.

В завершення хотіли б зауважити що LMS є доволі ресурсозатратним способом комунікації, і його використання потрібно уникати у випадках, коли це можливо. Також більш детально про LMS ви можете прочитати у цій статті.

Наприклад, використання анотації «@api» ідеально підходить для передачі даних між батьківським і дочірнім компонентами, оскільки пропонує прямий спосіб доступу до властивостей і методів дочірнього компонента.

З іншого боку, події корисні, для комунікації у зворотному боці від дочірніх компонентів до батьківських. Також за допомогою івент лістенерів ми можемо одночасно повідомляти декільком компонентам про зміни в стані нашого компонента.

Служба повідомлень Lightning рекомендована для обміну даними між непов’язаними компонентами, які не належать до однієї ієрархії. Це також корисно, коли компоненту потрібно надіслати повідомлення декільком компонентам, які підписані на той самий канал повідомлень.

Уважно розгляньте варіант використання та виберіть відповідний метод зв’язку, щоб забезпечити оптимальну продуктивність і ефективну передачу даних між компонентами.

Отже, якщо ви розробник Salesforce і ще не вивчили фреймворк LWC, ми наполегливо рекомендуємо спробувати його. Завдяки потужним функціям і простоті використання він швидко стає кращим фреймворком для створення веб-компонентів Lightning на платформі Salesforce.

У наступній, фінальній статті серії – ми плануємо розібратись у комунікації компонентів з Salesforce та Apex, які є способи їхні переваги, недоліки та рекомендації у застосуванні.

1 Вподобання

FYI Як альтернативу LMS можна використовувати PubSub модель, яка історично була “пращуром” LMS.
Також питання, чи вважається за потрібне видаляти EventListener (у disconnectedCallback хуці наприклад) в LWC? Ніколи не використовував цю фічу зі світу нативного JS у LWC…

1 Вподобання

Так дякую це має сенс. Ми вирішили не загострювати увагу на цьому зараз оскільки це серія статей для початківців і ми розібрали найбільш розповсюджені конструкції.

І так згідно документації LWC salesforce рекомендує видаляти Event Listeners у disconnectedCallback. Якщо ви додаєте eventListener до чогось, що не є частиною життєвого циклу компонента. Якщо цього не зробити, це може призвести до витоків пам’яті, що призведе до поступового зниження продуктивності всієї програми Lightning, доки користувач не закриє або не оновить вкладку свого браузера.