Як визначити та вирішити даремно відтворені зображення в React

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

З самого початку React змінив всю філософію створення веб-додатків, а згодом і спосіб мислення розробників. З його введенням Virtual DOM , React марки UI оновлює так ефективно , як вони можуть коли - або. Це робить досвід роботи з веб-додатком акуратним. Ви коли-небудь замислювались, як зробити ваші програми React швидшими? Чому веб-програми React середнього розміру все ще мають низьку ефективність? Проблеми полягають у тому, як ми насправді використовуємо React!

Як працює React

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

В основі React лежить синтаксис JSX та потужна здатність React створювати та порівнювати віртуальні DOM. З моменту свого випуску React вплинув на багато інших інтерфейсних бібліотек. Наприклад, Vue.js також спирається на ідею віртуальних DOM.

Кожна програма React починається з кореневого компонента. Ми можемо сприймати весь додаток як дерево, де кожен вузол є компонентом. Компоненти в React - це "функції", які відображають інтерфейс на основі даних. Це означає реквізит і державу, яку він отримує; сказати, що єCF

UI = CF(data)

Користувачі взаємодіють з інтерфейсом користувача та спричиняють зміну даних. Взаємодія - це все, що може зробити користувач у нашому додатку. Наприклад, натискання кнопки, переміщення зображень, перетягування елементів списку навколо та запити AJAX, що викликають API. Усі ці взаємодії лише змінюють дані. Вони ніколи не спричиняють жодних змін в інтерфейсі користувача.

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

UI1 = CF(data1)UI2 = CF(data2)

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

Changes = Difference(UI1, UI2)

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

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

Під час початкового процесу візуалізації React будує дерево DOM, як це -

Припустимо, що частина даних змінюється. Ми хочемо, щоб React зробив рендерінг лише тих компонентів, на яких безпосередньо впливає ця конкретна зміна. Можливо, пропустити навіть процес диференціації для решти компонентів. Скажімо, деякі зміни даних у Component 2на наведеному малюнку, і ці дані були передані з Rдо Bта потім 2. Якщо R повторно відтворює, тоді він відтворюватиме кожного з своїх дочірніх елементів, що означає A, B, C, D, і за допомогою цього процесу насправді React робить наступне:

На зображенні вище всі жовті вузли відображаються та диференціюються. Це призводить до втрати часу / ресурсів обчислень. Саме тут ми в першу чергу докладемо зусиль з оптимізації. Налаштування кожного компонента на відображення та розмежування лише тоді, коли це необхідно. Це дозволить нам відновити ці витрачені цикли процесора. По-перше, ми розглянемо спосіб виявлення даремно відтворених заявок.

Визначте витрачені візуалізації

Для цього існує кілька різних способів. Найпростіший метод - увімкнути опцію виділення оновлень у налаштуваннях React dev tools.

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

Давайте підемо з цього прикладу.

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

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

Використання методу shouldComponentUpdate

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

React надає простий метод життєвого циклу, щоб вказати, чи потрібно компоненту повторно рендерінг, а саме, shouldComponentUpdateщо запускається до початку процесу рендерингу. Повертається реалізація цієї функції за замовчуванням true.

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

Використання чистих компонентів

Працюючи над React, ви точно знаєте, React.Componentале з чим справа React.PureComponent? Ми вже обговорювали метод життєвого циклу shouldComponentUpdate, у чистих компонентах вже існує реалізація за замовчуванням, shouldComponentUpdate()з неглибоким порівнянням опори та стану. Отже, чистий компонент - це компонент, який повторно відтворює, лише якщо props/stateвін відрізняється від попереднього пропсу та стану .

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

Але що, якщо ми маємо функціональний компонент без стану, в якому нам потрібно реалізувати цей метод порівняння перед кожним повторним рендерингом? React має компонент вищого порядку React.memo. Це як React.PureComponentдля функціональних компонентів замість класів.

За замовчуванням він робить те саме, що shouldComponentUpdate (), який лише неглибоко порівнює об'єкт props. Але, якщо ми хочемо мати контроль над цим порівнянням? Ми також можемо надати спеціальну функцію порівняння як другий аргумент.

Зробіть дані незмінними

Що, якби ми могли використати, React.PureComponentале все одно мати ефективний спосіб визначити, коли будь-який складний реквізит або стан, такий як масив, об’єкт тощо, змінюються автоматично? Саме тут незмінна структура даних полегшує життя.

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

ES6 має оператор розповсюдження об'єктів, щоб це здійснилося.

Ми можемо зробити те ж саме для масивів:

Уникайте передачі нового посилання на ті самі старі дані

Ми знаємо, що щоразу, коли propsкомпонент змінюється, відбувається повторний візуалізація. Але іноді propsце не змінювалося. Ми пишемо код таким чином, що React вважає, що він змінився, і це також призведе до повторного відтворення, але на цей раз це даремно відтворений. Отже, в основному, нам потрібно переконатися, що ми передаємо різне посилання як реквізит для різних даних. Крім того, нам слід уникати передачі нового посилання на ті самі дані. Тепер ми розглянемо деякі випадки, коли ми створюємо цю проблему. Давайте розглянемо цей код.

Ось вміст BookInfoкомпонента, де ми відтворюємо два компоненти, BookDescriptionта BookReview. Це правильний код, і він працює нормально, але є проблема. BookDescriptionбуде повторно відображатись кожного разу, коли ми отримуємо нові дані відгуків як реквізит. Чому? Як тільки BookInfoкомпонент отримує новий реквізит, renderфункція викликається для створення свого дерева елементів. Функція візуалізації створює нову bookконстанту, що означає створення нового посилання. Отже, BookDescriptionотримаємо це bookяк посилання на новини, що призведе до повторного відтворення BookDescription. Отже, ми можемо перетворити цей фрагмент коду на наступний:

Тепер посилання завжди однакове, this.bookі новий об’єкт не створюється під час рендерингу. Ця філософія рендерингу застосовується до всіх propобробників подій, зокрема:

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

Підведенню

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