React
React — це бібліотека для опису того, як має виглядати інтерфейс за поточного стану, з тим, щоб хтось інший з’ясував, як довести до цього DOM. Модель уміщається в рядок — інтерфейс є функцією стану, — і майже всі складнощі з React походять із її порушення: нечистої роботи під час рендеру або стану, що лежить там, де не може бути джерелом правди.
- Модель
- UI = f(стан)
- Дві фази
- Рендер · Коміт
- Ефекти — для
- Зовнішніх систем
Описуйте, а не наказуйте
До React фронтенд-код здебільшого давав інструкції: знайди цей вузол, задай йому текст, додай клас, прибери той рядок. Кожна інструкція потребувала парної, що її скасовує, і кількість пар росла швидше за кількість фіч — тому старі фронтенд-бази ламалися в місцях, яких ніхто не чіпав.
React це перевертає. Ви пишете функцію, яка за поточним станом повертає опис інтерфейсу, а 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.
- Головна задача — документ, а не застосунок: важкий клієнтський рантайм нічого не дає.
- Додавати його заради одного віджета на серверній сторінці, де рантайм важчий за сам віджет.
Було корисно?
Поділіться з тим, хто працює над тією ж задачею.