РозробкаБудь-який рівень

React

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

Модель
UI = f(стан)
Дві фази
Рендер · Коміт
Ефекти — для
Зовнішніх систем

Описуйте, а не наказуйте

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

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

Зміна стану змушує React знову викликати ваші компоненти — це фаза рендеру, і вона чиста: вона створює опис інтерфейсу, і її можна перервати, відкинути або повторити. Лише фаза коміту торкається DOM, і вона стається один раз. Ефекти виконуються після коміту. Саме тому побічна дія, написана під час рендеру, поводиться дивно, а той самий код усередині ефекту — ні.

Схема ділить роботу на дві фази, і цей поділ — найнесучіша ідея цієї сторінки. Фаза рендеру викликає ваші компоненти, щоб отримати опис; вона має бути чистою, і React може виконати її двічі, перервати або викинути результат. Фаза коміту застосовує різницю до справжнього DOM і стається один раз. Ефекти виконуються після коміту, коли DOM уже актуальний.

Стан: де він живе і що в ньому має бути

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

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

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

Ефекти — це запасний вихід

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

Ви написали ефект, щоб…Зробіть натомість
Обчислити значення при зміні пропсівОбчислюйте під час рендеру. Якщо це справді дорого, загорніть у useMemo — але спершу виміряйте.
Скинути стан при зміні пропаДайте компоненту key. Новий ключ — це новий компонент зі свіжим станом, і коду не треба.
Надіслати запит при кліку на кнопкуРобіть це в обробнику події. Причина — клік, а не рендер.
Повідомити батька про зміну стануВикличте колбек там, де стається зміна, або підніміть стан, щоб не було про що повідомляти.
Завантажити дані при монтуванніЦе доречно, але беріть бібліотеку даних або завантажувач фреймворку — у голих ефектах немає скасування, кешу й захисту від перегонів.

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

Продуктивність у правильному порядку

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

Виправлення варто пробувати по порядку, бо перші два безкоштовні, а третє має власну ціну. Спершу опустіть стан нижче: якщо значення цікавить лише одне піддерево, покладіть його туди, і решта дерева перестане перерендерюватися. Далі передавайте елементи через children, а не створюйте їх усередині компонента, що часто перерендерюється: елемент, переданий пропом, не перерендерюється разом із батьком. І лише тоді беріть memo, useMemo і useCallback — кожен додає порівняння на кожному рендері й окупається лише тоді, коли пропущена робота справді більша за це порівняння.

Усередині теми продуктивності лежить одне правило коректності: ключі. Рендерячи список, React за key вирішує, який елемент якому пункту відповідає між рендерами. Використати індекс масиву як ключ — означає сказати React «пункт на позиції два — це той самий пункт, що й раніше», а це хибно, щойно список переупорядкували або щось прибрали із середини. Видимий результат — стан і фокус, що чіпляються не до того рядка. Беріть стабільний id з даних.

Серверні компоненти, коротко

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

Практичний наслідок — це аргумент про розмір бандла, а не про рендеринг. Бібліотека форматування дат, що використовується лише в серверному компоненті, не коштує користувачеві нічого, а завантаження даних переїжджає впритул до компонента, якому вони потрібні, без круга через API. Межа, яку треба тримати: інтерактивність — усе з onClick, станом чи ефектом — має жити в клієнтському компоненті, тож корисний патерн — серверне дерево з малими клієнтськими листками, і саме такої форми збудована ця енциклопедія.

Як це виявляється в реальній поставці

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

Де він вироджується

  • Похідні значення, збережені у стані, здатні розійтися з тим, з чого їх вивели.
  • Ефекти для синхронізації двох шматків стану — це обчислення рендеру, зроблене на кадр пізніше.
  • Завантаження в голому ефекті без очищення дає перегони й витоки.
  • Індекс масиву як ключ чіпляє стан і фокус не до того рядка після переупорядкування.
  • Мемоїзація всього за замовчуванням додає порівняння на кожен рендер і ховає справжню ціну.
  • Зміна стану на місці: React бачить те саме посилання й пропускає оновлення.
  • Усе в одному глобальному сховищі перетворює кожну зміну на подію рівня застосунку.

Коли це застосовувати

Застосовуйте, коли

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

Уникайте, коли

  • Контентна сторінка з дрібкою інтерактивності — HTML зі скриптом менший і швидший.
  • Команда хоче отримати конвенції, а не обирати їх, — саме це натомість дає Angular.
  • Головна задача — документ, а не застосунок: важкий клієнтський рантайм нічого не дає.
  • Додавати його заради одного віджета на серверній сторінці, де рантайм важчий за сам віджет.

Було корисно?

Поділіться з тим, хто працює над тією ж задачею.