Lifecycle Hooks in LWC

Lifecycle Hooks in LWC

Lightning Web Components (LWC) пропонують потужний та ефективний спосіб створення інтерактивних та адаптивних веб-додатків. Однією з ключових особливостей, яка сприяє їхній ефективності, є концепція Lifecycle Hooks. Ці хуки дозволяють розробникам втручатися в певні моменти життєвого циклу компонента, забезпечуючи його кастомізацію та оптимізацію.

Перше, що треба розуміти, це те, що веб компоненти Lightning мають життєвий цикл, керований фреймворком. Lifecycle Hooks - це методи, які викликаються на певних етапах цього життєвого циклу Lightning-компонента.

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

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

Lifecycle Hooks diagram

По суті, це означає, що кожного разу, коли батьківський і дочірній компоненти відображаються на сторінці, порядок перехоплювачів життєвого циклу буде таким:

  1. constructor() викликається на батьківському
  2. connectedCallback() викликається на батьківському
  3. constructor() викликається на дочірньому елементі
  4. connectedCallback() викликається на дочірньому елементі
  5. renderedCallback() викликається на дочірньому елементі
  6. renderedCallback() викликається на батьківському

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

constructor()

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

Приклад:

constructor() {
    super();
    this.state = { isLoading: true };
}

connectedCallback()

Викликається, коли компонент вставлено в DOM. Він викликається тільки один раз за цикл життя компоненту.

Це часто використовується для таких завдань, як отримання даних з сервера або ініціалізація сторонніх бібліотек.

Наприклад, використовуйте його для:

  • Встановлення зв’язку з поточним документом або контейнером і координації поведінки з навколишнім середовищем.
  • Виконання завдань ініціалізації, таких як отримання даних, налаштування кешу або event listener.
  • Під’єднання до Lightning Message Service (LMS).

Хук connectedCallback() викликається з початковими властивостями, переданими компоненту.

Приклад:

connectedCallback() {
    this.loadData();
}

Порада
Для перевірки того, чи компонент підключений до DOM, ви можете використовувати this.isConnected.

renderedCallback()

Викликається після рендерингу шаблону компонента. Це корисно для виконання дій над DOM-елементами після їхнього рендерингу.

Цей хук специфічний для Lightning Web Components і не належить до специфікації HTML для власних елементів.

Зміна властивості, яка знаходиться безпосередньо в шаблоні (template) компоненту, перемальовує компонент та викликає повторне виконання цього методу.

Тобто renderedCallback() буде викликатися на будь-яку зміну стану компонента, тобто необмежену кількість разів за один цикл життя.

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

Якщо ви використовуєте renderedCallback() для виконання одноразової операції, вам потрібно вручну відстежувати це (наприклад, використовуючи приватну властивість hasRendered).

Для цього потрібно створити приватну булеву властивість як, наприклад, hasRendered, щоб відстежувати, чи був виконаний renderedCallback(). При першому виклику renderedCallback() виконайте одноразову операцію і встановіть hasRendered = true. Якщо hasRendered = true, не виконуйте операцію.

Дивіться приклад нижче:

import { LightningElement, track } from 'lwc';

export default class RenderedCallbackInLWC extends LightningElement {

    @track properties;

    @track hasRendered = true;

    renderedCallback() {
        if (hasRendered) {
            this.properties = 'set by renderedCallback';
            console.log('properties ' + this.properties);
            hasRendered = false;
        }
    }

    handleButtonClick() {
        this.properties = 'set by buttonClick';
    }
}

Використовуйте renderedCallback() для взаємодії з інтерфейсом користувача компонента. Наприклад, використовуйте його для:

  • обчислення розміру вузлів.
  • виконання змін пов’язаних з візуальним виглядом сторінки

render()

Викликайте цей метод для оновлення інтерфейсу користувача. Він може бути викликаний перед або після connectedCallback().

Рідко коли викликають render() у компоненті. Основний випадок використання - умовне відображення шаблону. Визначте бізнес-логіку для визначення, який шаблон (файл HTML) використовувати. Метод повинен повертати дійсний HTML-шаблон.

Наприклад, уявіть, що у вас є компонент, який може бути відображений двома різними способами, але ви не хочете комбінувати HTML в одному файлі. Створіть кілька файлів HTML у пакеті компонента. Імпортуйте їх обидва і додайте умову в метод render(), щоб повернути відповідний шаблон в залежності від стану компонента.

Примітка
Метод render() не є технічно хуком життєвого циклу. Це захищений метод класу LightningElement. Хук зазвичай повідомляє вам, що щось сталося, і він може або не може бути на ланцюгу прототипів. Метод render() повинен існувати на ланцюгу прототипів.

disconnectedCallback()

Викликається, коли елемент видаляється з DOM, наприклад під час закриття сторінки. Цей хук також викликається від батька до дитини.

Використовуйте disconnectedCallback() для очищення роботи, виконаної в connectedCallback(), такої як очищення кешу або видалення event listeners.

Ви також можете використовувати цей хук для відписки від LMS.

Приклад:

disconnectedCallback() {
    window.removeEventListener('resize', this.handleResize);
}

errorCallback(error, stack)

Викликається, коли дитячий компонент видає помилку.

Цей хук життєвого циклу є специфічним для Lightning Web Components і не належить до специфікації HTML для власних елементів.

Реалізуйте цей хук для створення компонента-обробника помилок, який перехоплює помилки в усіх дитячих компонентах у своєму дереві.

Використовуйте errorCallback() цього компонента для ведення журналу логів і відображення альтернативного виду для користувачів, де роз’яснюється, що сталося і що робити далі.

Цей метод працює, як блок catch{} в JavaScript для компонентів, які генерують помилки у своїх хуках життєвого циклу або event handler, визначених у шаблоні HTML.

Приклад:

errorCallback(error, stack) {
    this.showError('Щось пішло не так!');
}

Підсумовуючи інформацію, маємо таке базове використання хуків життєвого циклу:

  1. Отримання даних:

Використання connectedCallback для отримання даних, коли компонент вставлено в DOM.

  1. Маніпуляції з DOM:

Використовуйте renderedCallback для взаємодії з DOM після його рендерингу.

  1. Очищення:

Використовуйте disconnectedCallback для очищення ресурсів при видаленні компонента.

Життєвий цикл LWC і wire-сервіси

Поговоримо про те, де в життєвому циклі LWC розташовані wire-сервіси.

Нижче наведений приклад коду з використанням wire та основних хуків LWC.

import {LightningElement, wire} from 'lwc';
import {getObjectInfo} from 'lightning/uiObjectInfoApi';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';

export default class LwcLifeCycle extends LightningElement {

    objectInfoData;
    counter = 1;

    @wire(getObjectInfo, {objectApiName: ACCOUNT_OBJECT})
     objectInfo({error, data}) {
         console.log(this.counter++ + ' - wire service returned with {data= '+data+' & error = '+error+'}');
         if(data) {
            this.objectInfoData = data;
         }
     }

    constructor() {
        super();
        console.log(this.counter++ +' - constructor invoked');
    }

    connectedCallback() {
         console.log(this.counter++ +' - connectedCallback invoked');
    }

    renderedCallback() {
        console.log(this.counter++ +' - renderedCallback invoked');
    }
}

Тепер якщо додати компонент, з цим JS файлом на Lightning Page у консолі браузера можна побачити результати щодо того, де розташовані wire-сервіси в життєвому циклі компонента.

Ви отримаєте лог схожий на такий:

Можна зробити висновки, що виконання хуків з wire-сервісами виконуються у такій послідовності

  1. Конструктор
  2. Wire-сервіс без даних
  3. сonnectedCallback
  4. renderedCallback
  5. Wire-сервіс з даними

Виклики Apex

Ми можемо викликати Apex за допомогою wire-сервісів як вказано вище. Окрім цього є ще можливість викликати Apex методи імперативно.

Imperative Apex call - це спосіб імперативного виклику методу Apex за допомогою JavaScript. Це означає, що замість того, щоб покладатися на декоратор @wire, ви можете викликати метод Apex безпосередньо з вашого JavaScript коду. В імперативному Apex ми явно викликаємо методи Apex, а повернуте значення можна використовувати безпосередньо в компоненті.

Нижче наведений приклад, в якому значення, що повертає Apex метод, викликаний в JavaScript, ми записуємо у приватну властивість contacts:

import { LightningElement, wire } from 'lwc';
import getContacts from '@salesforce/apex/ContactController.getContacts';

export default class ImperativeMethodCall extends LightningElement {
    
    contacts;
    error;

    connectedCallback() {
        getContacts()
        .then(result => {
            this.contacts = result;
        })
        .catch(error => {
            this.error = error;
        });
    }
}

Best practices

  1. Не рекомендовано використовувати renderedCallback() з @wire та @track у наступних умовах, які призводять до нескінченного циклу:
  • renderedCallback() оновлює зміну конфігурації @wire, а @wire запускає рендер.
  • renderedCallback() оновлює властивість @track, яка запускає рендер.
  1. Використовуйте renderedCallback() для взаємодії з інтерфейсом користувача компонента. Наприклад, використовуйте його для обчислення розміру елементів.

  2. Хук життєвого циклу renderedCallback() часто використовується в парі з connectedCallback(). Використовуйте renderedCallback(), щоб зрозуміти стан “внутрішнього” світу (інтерфейсу та властивостей компонента), а connectedCallback() - щоб зрозуміти стан “зовнішнього” світу (оточення, що містить компонент).

  3. Зробіть логіку конструктора мінімальною. Конструктор призначений для налаштування початкових властивостей і стану компонента. Уникайте розміщення тут важкої логіки або HTTP-запитів.

  4. Використовуйте connectedCallback для ініціалізації. Якщо вам потрібно отримати дані або виконати більш суттєву ініціалізацію, використовуйте connectedCallback, оскільки він гарантує, що компонент підключений до DOM.

  5. Очистка у disconnectedCallback. Завжди видаляйте event listeners, таймери або підписки у від’єднаному зворотному виклику, щоб запобігти витоку пам’яті.

  6. Уникайте прямих маніпуляцій з DOM. Замість ручної маніпуляції з DOM, розумно покладатися на прив’язку даних, залишаючи оновлення DOM самому фреймворку.

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

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

  9. Враховуйте залежності компонентів. Зберігайте чітке розуміння того, як компоненти взаємопов’язані та залежать один від одного. Переконайтеся, що хуки життєвого циклу батьківського та дочірнього компонентів скоординовані, якщо це необхідно.

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

  11. Тестуйте методи життєвого циклу. Переконайтеся, що методи життєвого циклу охоплені вашими модульними тестами. Це допомагає підтримувати стабільність компонентів.

  12. Звертайтеся до офіційної документації. Завжди звертайтеся до офіційної документації конкретного фреймворку, який ви використовуєте (наприклад, LWC) для отримання детальних інструкцій та рекомендацій щодо хуків життєвого циклу.

Висновки

Отже, можна сказати, що життєві цикли Lightning Web Components (LWC) - це важливий аспект створення компонентів у мережевому середовищі Salesforce.

Розуміння цих хуків допомагає розробникам керувати поведінкою компонентів на кожному етапі їх життєвого циклу. Від конструктора до методу render(), кожен хук має свою роль і завдання.

Правильне використання цих хуків дозволяє створити надійні та продуктивні компоненти LWC. Розробники можуть використовувати їх для ініціалізації, взаємодії з іншими компонентами та керування компонентами на кожному етапі їх життєвого циклу. Хуки життєвого циклу допомагають зробити розробку LWC ефективною та потужною.

3 Вподобання