Прокачайся в код-рев’ю: для перших 50 учасників — курс безкоштовний

час читання: 6 хв

React JS: DOM і процес узгодження

Що таке Document Object Model і в чому різниця між реальним і віртуальним DOM? У статті розглядаються ці два поняття та те, як влаштований процес узгодження.

Автор статті — EPAM Senior Software Engineer Анжані Тараїл.

Інші статті автора:

Що таке DOM?

DOM означає Document Object Model (об’єктна модель документа). Це така собі організована версія HTML на вебсторінці чи в застосунку. Він відображає весь користувацький інтерфейс вебзастосунку у вигляді деревоподібної структури:

Представлення HTML та DOM

Нижче наведена спрощена схема роботи рендерингу в браузері:

Як працює рендеринг

Коли в UI (користувацькому інтерфейсі) вебзастосунку відбуваються зміни в результаті взаємодії з користувачем або динамічне оновлення вмісту, має бути здійснена їх обробка через рендеринг браузера для оновлення відображення відповідним чином. Ось як працює рендеринг, коли в UI відбуваються зміни:

Як працює рендеринг, коли відбуваються зміни в інтерфейсі користувача

Складнощі рендерингу

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

AngularJS — це популярний JavaScript-фреймворк, який використовує реальний DOM. Одним із його недоліків є те, що він може бути повільним під час рендерингу складних сторінок, оскільки мусить оновлювати все DOM-дерево щоразу, коли відбуваються зміни.

Віртуальний DOM

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

React, широко застосовуваний фреймворк, використовує віртуальний DOM.

Коли до UI додаються нові елементи, створюється віртуальний DOM, який поданий у вигляді дерева. Кожен елемент є вузлом цього дерева. Якщо стан будь-якого з елементів змінюється, створюється нове дерево віртуального DOM. Потім це дерево порівнюється, або «диференціюється» (алгоритм диференціювання) з попереднім деревом віртуального DOM.

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

Алгоритм диференціювання: ефективне порівняння дерев

Процес виявлення змін у Document Object Model після маніпуляцій полегшується за допомогою алгоритму диференціювання. Цей алгоритм відіграє вирішальну роль в оптимізації порівняння оновлених віртуальних DOM зі знімками до модифікації. Розуміючи, як порівнюються дерева, ми бачимо, наскільки ефективно працюють оновлення та рендеринг у фреймворках на кшталт React.

  • Порівняння кореневих елементів

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

  • Різнотипні елементи DOM

Якщо кореневі елементи відрізняються за типом, React знищує старе дерево й будує нове з нуля. Старі DOM-вузли знищуються, запускається команда componentWillUnmount() в складових екземплярах. Нове дерево передбачає вставку нових DOM-вузлів, що призводить до виконання команди UNSAFE_componentWillMount(), а потім componentDidMount(). Важливо, що будь-яка інформація, пов’язана зі старим деревом, втрачається.

  • Ідентичні елементи DOM

У випадках, коли кореневі елементи мають однаковий тип, React зберігає базовий DOM-вузол й оновлює тільки змінені атрибути:

<div className="before_changes" title="Changes" />

<div className="after_changes" title="Changes" />

Порівнюючи ці два елементи, React змінює виключно атрибут className базового DOM-вузла. Навіть під час оновлення стилів React оновлює лише змінені властивості:

<div style={{color: ’red’, fontWeight: ’bold’}} />

<div style={{color: ’green’, fontWeight: ’bold’}} />

React розуміє, що змінюватись повинен лише стиль кольору, тому він не змінює fontWeight.

  • Уніфіковані компоненти

Коли компоненти оновлюються, їхні екземпляри залишаються незмінними, забезпечуючи безперервність стану всіх рендерів. Для цього React оновлює пропси базового екземпляра компонента, щоб вони відображали новий елемент. Потім на наявному екземплярі викликаються команди UNSAFE_componentWillReceiveProps(), UNSAFE_componentWillUpdate() і componentDidUpdate(). Викликається команда render(), яка ініціює рекурсивне застосування алгоритму диференціювання до попереднього та поточного результатів.

  • Рекурсивна оцінка дітей

За замовчуванням, за рекурсивного обходу дочірніх DOM-вузлів React одночасно ітерує обидва списки дочірніх елементів. Він генерує мутації щоразу, коли з’являються розбіжності. Наприклад, наступна трансформація демонструє плавне перетворення дерева, коли елемент додається до children:

До:

<ul>

  <li>apple</li>

  <li>orange</li>

</ul>

Після:

<ul>

  <li>apple</li>

  <li>orange</li>

  <li>banana</li>

</ul>

У цьому випадку React узгоджує дерева <li>apple</li> та <li>orange</li> й інтегрує дерево <li>banana</li>.

Однак вставка елемента на початку може призвести до погіршення ефективності, якщо його не реалізувати належним чином:

До:

<ul>

  <li>Tata</li>

  <li>Toyota</li>

</ul>

Після:

<ul>

  <li>Mahindra</li>

  <li>Tata</li>

  <li>Toyota</li>

</ul>

Тут React змінив усіх children замість того, щоб визнати потенціал для збереження піддерев <li>Tata</li> та <li>Toyota</li>. На таку відсутність ефективності слід звернути увагу.

  • Застосування ключів

Щоб вирішити цю проблему, React застосовує ключові атрибути. Коли дітям призначаються ключі, React використовує їх для зіставлення дітей у початковому дереві з елементами в наступному. Удосконалення попереднього прикладу для оптимізації перетворення дерев:

До:

<ul>

  <li key="2911">Tata</li>

  <li key="2912">Toyota</li>

</ul>

Після:

<ul>

  <li key="2910">Mahindra</li>

  <li key="2911">Tata</li>

  <li key="2912">Toyota</li>

</ul>

React визначає, що елемент із ключем '2910' новий, тоді як елементи з ключами '2911' і '2912' були переміщені.

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

<li key={item.id}>{item.name}</li>

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

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

Узгоджувач Stack

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

Узгоджувач Stack поділяється на дві основні фази: монтування та оновлення. Під час монтування він створює новий DOM-елемент для кожного React-компонента, а потім приєднує його до DOM-дерева. Під час оновлення узгоджувач Stack порівнює стару й нову версії дерева компонентів, а потім вносить необхідні зміни до DOM, щоб відобразити нове дерево.

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

Узгоджувач Stack — це простий, але ефективний спосіб оновлення DOM у разі React-компонентів. Він використовується в React 15 і більш ранніх версіях. У React 16 узгоджувач Stack було замінено на віртуальний узгоджувач DOM, який є більш ефективним методом оновлення DOM.

Деякі з ключових понять узгоджувача Stack:

  • Монтування: процес створення нового DOM-елемента для кожного React-компонента, а потім приєднання його до DOM-дерева.
  • Оновлення: процес порівняння старої та нової версій дерева компонентів, а потім внесення необхідних змін до DOM для відображення нового дерева.
  • Рекурсивний алгоритм: алгоритм, який викликає сам себе для виконання завдання.
  • Мемоізація: метод кешування результатів попередніх обчислень для уникнення їх повторення.
  • Віртуальний DOM: полегшена презентація DOM, яку можна використовувати для ефективного оновлення реального DOM.

Узгоджувач Fiber

Узгоджувач Fiber — це новий алгоритм узгодження, реалізований у React 16. Він призначений для покращення ефективності та масштабованості React-застосунків через впровадження асинхронного рендерингу та нового алгоритму планування.

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

Ці волокна можуть виконуватися незалежно одне від одного й пріоритезуватися за важливістю. Це дозволяє React більш ефективно планувати оновлення, а отже покращуються ефективність і масштабованість.

Деякі з ключових особливостей узгоджувача Fiber:

  • Волокна — це маленькі одиниці роботи, які являють собою компонент у дереві React. Кожне волокно має посилання на батьківське волокно, дочірні волокна й пов’язаний із ним вузол DOM.
  • Ворклети — це асинхронні функції, які можна запланувати до виконання під час узгодження. Це дає React змогу асинхронно виконувати ресурсомісткі операції, як-от анімація та декодування зображень, не блокуючи основний потік.
  • Узгоджувач Fiber використовує новий алгоритм планування для визначення пріоритетності оновлень залежно від їхньої важливості. Це гарантує, що критично важливі оновлення будуть відображатися першими, тоді як менш важливі можуть бути відкладені на пізніше.
  • Узгоджувач Fiber є більш складним алгоритмом, ніж узгоджувач Stack, але він дає значні покращення ефективності та масштабованості. Це алгоритм узгодження в React 16 і пізніших версіях за замовчуванням.

Коротко про ключові відмінності між узгоджувачами Fiber і Stack:

Відмінності між пристроєм узгодження волокон і пристроєм узгодження стека

Висновок

Реальний DOM являє собою структуру вебсторінки, а віртуальний DOM — це легка абстракція, яку React використовує для ефективного оновлення. Узгодження в React — це метод, за допомогою якого користувацький інтерфейс (UI) оновлюється для відображення змін у стані компонента. Алгоритм узгодження містить настанови, які React використовує для визначення найбільш ресурсоефективного підходу до оновлення UI. React використовує віртуальний DOM для керування цими оновленнями UI. Це робить React високопродуктивною бібліотекою для побудови користувацьких інтерфейсів.

React-розробник: джерела вивчення та перспективи у майбутньому