Найкращі практики роботи з React JS: поради з оновленої документації

pic

Фото: Sora Shimazaki на Pexels

Останнє оновлення: 2024–08–30

Нещодавно на офіційному вебсайті React були випущені нові документи. Це найкращий спосіб вивчити фреймворк.

pic

https://react.dev/learn

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

Але тепер це не потрібно! Уся ця езотерична інформація тепер доступна в легкому для читання форматі. Молоді таланти, що наступають вам на п'яти, заберуть вашу роботу завдяки новим React документаціям!

Єдиний спосіб залишатися попереду – це змиритися, сісти і уважно прочитати кожне слово в нових документах.

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

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

Вступ

Нова документація React має дві секції: “Learn” та “API Reference.”

Ця стаття охоплює секцію “Learn”. Її слід читати послідовно.

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

Розділ 1: Про React

1. React був спочатку створений Джорданом Уолке.

На сьогоднішній день Джордан Уолке більше не працює в Meta.

2.

React має 21 основних учасників команди.

Порівняйте з 20 учасниками команди Vue та 6 учасниками команди Solid. Svelte не публікує кількість своїх основних учасників.

Розділ 2: Початок роботи

3. Документація використовує CodeSandbox для інтерактивних прикладів коду.

Документація також зазначає, що React підтримується кількома іншими сервісами для пісочниць. Вони відкрито називають CodeSandbox, StackBlitz та CodePen.

4. Нова документація взяла на озброєння підхід Vue для пояснення різних варіантів використання React.

Від “додавання деякої інтерактивності” до “більш складних додатків” і навіть “великих додатків”, нова документація чудово пояснює різні рівні використання React.

5.

Додавання React до простого HTML-сторінки дуже просте. Це навіть не потребує кроку збірки.

Просто додайте React як тег скрипту, напишіть компонент і відобразіть його на корені.

Якщо ви хочете додати підтримку JSX, можна налаштувати простий крок збірки за допомогою babel-cli.

Якщо ви хочете додати підтримку JSX без кроку збірки, обов'язково використовуйте HTML стартовий файл, наданий документацією React, який містить окремий скрипт Babel для JSX. Зверніть увагу, що це дуже повільно, тому його слід використовувати тільки для тестування або демонстрацій.

6. Ви можете мати кілька коренів React.

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

Нова документація більше не рекомендує Create React App.

Create React App був зручним, але це вже застарілий інструмент, який так і не виправив важливі баги. Якщо не вірите, подивіться на проблеми у репозиторії GitHub. Документація тепер рекомендує популярні альтернативи, такі як Vite та Parcel.

Next.js рекомендується для готових до продакшена проєктів.

Це не є несподіванкою. Next.js – це хороший вибір, особливо зараз, коли він надає більше гнучкості у створенні макетів сторінок. Інші рекомендації включають Gatsby, Remix та Razzle.
Є чудовий список рекомендацій інструментів для створення власного інструментарію.

Якщо ви хочете працювати самостійно, документація пропонує розумний і сучасний список опцій. Приємним сюрпризом для мене стало те, що немає згадок про Lerna, що логічно після недавніх проблем та дискусій. Натомість для роботи з монорепозиторіями (monorepos) рекомендуються Nx та Turborepo.

Рекомендовані редактори

У списку редакторів фігурують VS Code, WebStorm, Sublime Text та Vim. Тут також немає великих сюрпризів. Я б лише додав, що будь-який редактор від JetBrains чудово впорається із завданнями, а не лише WebStorm.
Також, Sublime Text — це мем. Дан Абрамов (Dan Abramov) згадував його лише тому, що досі почувається винним через той момент на конференції, коли на екрані з'явилося сповіщення про покупку ліцензії для Sublime Text (ліцензія у нього, до речі, була, просто виникла технічна проблема).

Що стосується Vim, то його сайт виглядає як з 90-х, через що сам редактор стає практично неприємним у використанні. Ймовірно, в майбутньому його замінить Neovim у документації.

11. ESLint обов’язковий для коректного використання React.

Як є на даний момент, будь-яке правильне використання React вимагає, щоб eslint-plugin-react-hooks працював постійно. У майбутньому це може бути реалізовано через Rome, крок компіляції або інший інструмент.

12. Prettier — король.

Без сюрпризів. Документація рекомендує використовувати Prettier, і це правильно.
Якщо ваш код використовує і ESLint, і Prettier, рекомендується використовувати preset eslint-config-prettier, щоб ESLint лише ловив логічні помилки, а важливу роботу виконував Prettier, як і годиться.

13. Немає розширення React Developer Tools для браузера Safari.

Є версії для Chrome, Firefox та Edge.

Для дебагу на Safari є обхідний шлях, який передбачає використання окремого пакету react-devtools npm, який також можна використовувати для тестування React Native додатків.

14. React додатки складаються з компонентів.

Документація прямо каже, що основною абстракцією фреймворку є компоненти. Що тут скажеш про потужного вбивцю MVC, про якого ще не говорили?

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

15.

Для вивчення JavaScript, документація рекомендує MDN та javascript.info.

Я не знав про javascript.info, але цей ресурс є відкритим і виглядає цілком надійно.

16. Рекомендують онлайн-інструмент для конвертації HTML в JSX

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

До речі, цей інструмент — справжня золота жила, адже він також включає конвертацію SVG в JSX, CSS у Tailwind і багато іншого.

17. React не нав'язує способу додавання CSS.

У найпростішому випадку ви додасте тег \ до вашого HTML. Якщо ви використовуєте інструмент для збірки або фреймворк, зверніться до його документації, щоб дізнатися, як додати стилі до вашого проєкту.

18.

Можна уявити фігурні дужки в JSX як “вихід” до JavaScript.

Це гарний спосіб їх уявити. Ви можете вставляти фігурні дужки в значення атрибутів JSX або всередині контенту тегу JSX.

Фігурні дужки — це “вікно у світ JavaScript”.

19. Оператори ? та && рекомендуються, якщо вам потрібно умовне рендеринг всередині JSX.

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

20. Атрибут “key” чітко пояснений.

Часто непорозумілим терміном є "key", але в новій документації він пояснений досить добре. Це так добре, що я навіть скопіюю всю цю частину:

Для кожного елемента в списку [компонентів] ви повинні передавати рядок або число, яке унікально ідентифікує цей елемент серед його братів.
Зазвичай, значення для ключа має приходити з ваших даних, наприклад, з ID з бази даних. React буде покладатися на ваші ключі, щоб зрозуміти, що сталося, якщо ви пізніше вставите, видалите або зміните порядок елементів.

Як ми побачимо пізніше, "key" також використовується для складнішого випадку скидання стану компонента. Але я все ж вважаю пояснення повним, оскільки цей складний випадок є просто наслідком згадуваної раніше “унікальності”.

І не забувайте: Ключі не повинні змінюватися, або вони втратять своє призначення! Не генеруйте їх під час рендерингу. Замість цього використовуйте стабільний ID, що базується на даних.

21.

Документація дуже добре пояснює, що є правилом, а що — умовністю.

Наприклад, вони пояснюють, що useState повертає два значення, і лише за умовою ми називаємо їх [something, setSomething].

Інший приклад: вони пояснюють, що зазвичай обробники подій називаються як “handle” із додаванням назви події. (onClick={handleClick}, onMouseEnter={handleMouseEnter} тощо).

Натомість, Hooks — це функції, які, як правило, повинні починатися з "use".

22. “Якщо ви хочете використовувати hooks у умові або циклі, витягніть новий компонент і покладіть його туди.”

Ви почули це тут вперше.

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

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

23. “Піднімання стану” (Lifting state up) — в центрі уваги документації.

Цей відомий шаблон рефакторингу настільки поширений, що він детально пояснений на сторінці “Quick Start”. Це, ймовірно, допоможе стримати тих більш наполегливих розробників.

24.

Перетворення UI макету в ієрархію компонентів — це творчий процес.

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

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

25. Немає проблем, якщо ви зверху або знизу.

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

26.

Мінімальний стан — це круто.

Документація справді наголошує на принципі DRY (Don’t Repeat Yourself) стосовно стану. Зокрема, немає потреби створювати новий стан, якщо його можна обчислити на основі існуючого стану.

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

27. Різниця між Props та State чітко пояснена.

“Props схожі на аргументи, які ви передаєте функції,” в той час як “State — це як пам’ять компонента.”

28.

Цілком нормально створити новий компонент лише для зберігання спільного стану.

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

29. Hooks називаються "hooks", тому що вони дозволяють "вмикати" функціональність в цикл рендеру компонента.

Завжди добре знати, до чого ви підключаєтесь. Тепер ви знаєте.

30. В певному сенсі React використовує "двостороннє зв'язування даних".

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

“Односторонній потік даних” — це передача даних з верхньої частини ієрархії компонентів до нижньої. “Зворотний потік даних” відбувається, коли компонент глибоко в ієрархії потребує оновити стан на верху (зазвичай через введення користувача).

Підступ, і причина, чому React технічно не є "двостороннім зв'язуванням даних", полягає в тому, що React дуже чітко визначає "зворотний потік даних": Розробник насправді повинен написати обробники подій onChange.

Розділ 3: Описування інтерфейсу користувача

31. Документація рекомендує бібліотеки компонентів Chakra UI та Material UI.

Так, саме в такому порядку.
Chakra UI рекомендується перед Material UI.

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

32. У React є філософія "інтерактивність перш за все."

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

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

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

Лінукс-бумерам рекомендується нанести алое на опік.

Щоб бути чесними, документація чітко вказує, що React також може бути використаний для “додавання розпилень інтерактивності.”

33. У React логіка рендеринга та розмітка живуть разом.

Ще один принцип, зазначений у документації, це те, що логіка та розмітка повинні бути разом (тобто в одному файлі). Це добре, оскільки це “забезпечує їх синхронізацію при кожному редагуванні.”

Примітка автора: Можливо, саме через це Styled Components та Tailwind стали такими популярними, оскільки, подібним чином, вони дозволяють стилям також жити разом з розміткою та логікою в одному файлі.

34.

React не займає жодної позиції в дебатах про “default vs named exports”.

На їхню користь, вони чітко пояснюють недоліки default exports, говорячи, що “ви могли б написати import Banana from './button.js', і це все одно забезпечить вам той самий default export.”

Однак їхнє остаточне рішення з цього питання — це просто спостереження, що “люди часто використовують default exports, якщо файл експортує тільки один компонент, і використовують named exports, якщо він експортує кілька компонентів та значень.”

Вони також не соромляться мати “один default export і численні named exports.”

35. Ніколи не визначайте компонент всередині іншого компонента!

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

36.

Цілком нормально створювати компоненти, навіть якщо вони не є багаторазовими.

React — це “компоненти до самого низу”, тому завжди буде хоча б один немножко повторюваний компонент: верхній компонент “app”.

Але нормально мати навіть більше одноразових компонентів, тому що “компоненти — це зручний спосіб організувати код UI та розмітку, навіть якщо деякі з них використовуються лише один раз.”

37. Фрагменти дозволяють повертати більше одного елемента.

Документація пояснює, чому компонент не може повернути більше одного елемента: це тому що “JSX під капотом перетворюється на прості об’єкти JavaScript.”
“Не можна повернути два об’єкти з функції без того, щоб обернути їх в масив.”

Але не бійтеся, адже ви можете повернути два теги JSX, “обернувши їх в інший тег або Фрагмент.”

Фрагменти — це “порожні теги” ( <>…), які дозволяють групувати елементи без залишення слідів в HTML-дереві браузера.

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

38.

aria-* та data-* — це єдині атрибути JSX, які записуються через дефіс.

Це пов’язано з історичними причинами.

Загалом все в JSX пишеться в стилі camelCase, навіть властивості інлайн-стилів, тому що JavaScript має обмеження щодо імен змінних.

До того ж, оскільки class є зарезервованим словом, у React потрібно використовувати className замість нього.

39. “Подвійні фігурні дужки” — офіційний термін.

Це явна назва для передачі об’єкта JavaScript у JSX, де об’єкт потрібно обернути ще однією парою фігурних дужок: {{}}.

Це не спеціальний синтаксис, це просто об’єкт JavaScript, який знаходиться всередині фігурних дужок JSX.

Не слід плутати з “синтаксисом піхви”, коли з функції-стрілки повертається об’єкт: ({}).

40.

Пропси, які ви можете передавати HTML-тегам, є заздалегідь визначеними.

Наприклад, до таких пропсів, як “className”, “src”, “alt”, “width” і “height”, ви можете передавати їх у тег <img>.

Але ви можете передавати будь-які пропси до своїх компонентів.

41. Якщо ви постійно використовуєте spread, можливо, ви поводитеся аморально.

Деякі компоненти передають усі свої пропси своїм дітям. Оскільки вони не використовують жоден з пропсів безпосередньо, може бути доцільно використовувати скорочений синтаксис "spread": {...props}.

Однак документація попереджає, що “синтаксис spread слід використовувати обережно. Якщо ви використовуєте його в кожному компоненті, щось не так. Часто це вказує на те, що вам слід розділити ваші компоненти.”

Обирайте, для кого ви застосовуєте spread.

42.

Деякі компоненти мають “діру”, яку можна “заповнити”.

Ви можете уявити компонент з пропсом children як такий, що має “діру”, яку можна “заповнити” дочірніми компонентами за допомогою довільного JSX.

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

43.

Компонент може отримувати різні пропси з часом.

Поверхневе розуміння різниці між станом (state) та пропсами (props) може змусити вас подумати, що пропси не змінюються.

Але це не так! Батьківський компонент може мати стан (state), який змінюється з часом і передає його дочірньому компоненту через пропси (props).

Проте з точки зору дочірнього компонента пропси є “незмінними” — термін з комп'ютерних наук, що означає “незмінні”.

Отже, коли дочірній компонент потребує зміни своїх пропсів (наприклад, у відповідь на взаємодію з користувачем або нові дані), він повинен “попросити” батьківський компонент створити та передати “нові пропси” за допомогою згаданого “оберненого потоку даних” (inverse data flow).

Ми можемо сказати, що пропси є лише знімками стану на момент часу: кожен рендер отримує нову версію пропсів. (І, як ми побачимо пізніше, стан (state) теж є знімком стану на момент часу)

44.

Документація рекомендує використовувати тернарні оператори “в поміркованих кількостях”.

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

Якщо ваші колеги-розробники наполягають на створенні соусу зі спагетті з тернарних операторів, можна відправити їх до React документації, або ось сюди:

[

Програмісти міленіалів та покоління Z відкидають спагетті-код своїх батьків.

Молоді покоління роблять сміливі кроки для відкидання поганого коду, також відомого як спагетті-код.

levelup.gitconnected.com

](https://levelup.gitconnected.com/millennial-and-gen-z-programmers-reject-their-parents-spaghetti-code-cf481bff8cdd?source=post_page-----1c65570e785d--------------------------------)

45.

Не ставте числа зліва від &&.

Старожили знають це правило.

Якщо ви використовуєте && для умовного рендерингу, ви зіткнетеся з проблемою з нулем. Швидше за все, ви отримаєте маленьку незформатовану цифру 0, що не пасуватиме до вашого блискучого шрифта sans-serif в матеріальному дизайні.

Математики ненавидять це.

46. Іноді краще уникати скорочень і просто умовно присвоювати JSX змінній.

Тернарні оператори та && — це зручно, але якщо вони вам заважають, можна використовувати старі добрі змінні для зберігання умовного JSX.

Мені подобається, що документація прямо каже, що нормально писати стандартний JavaScript. Цукор гарний, але ванільний JS і так солодкий.

47.

Документація рекомендує використовувати “crypto.randomUUID()” або пакет на кшталт “uuid.”

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

Так, вони рекомендують crypto.randomUUID() замість пакета “uuid.”

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

48. Ваші компоненти не отримуватимуть key як пропс.

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

Якщо вашому компоненту потрібен ID, що використовувався як ключ, вам потрібно передати його як окремий пропс.

49.

React припускає, що кожен компонент, який ви пишете, є чистою функцією.

Не робіть нечистих речей за спиною React. React побудований на концепції чистих функцій.

Чиста функція має такі характеристики:

  • Вона займається тільки своєю справою. Вона не змінює жодних об'єктів чи змінних, які існували до її виклику. (Це означає, що вона не мутує змінні поза межами своєї області видимості і не змінює своїх вхідних даних. В React, "props", "state" та "context" вважаються вхідними даними.)
  • Ті ж вхідні дані — той самий результат. За умови однакових вхідних даних, чиста функція завжди повинна повертати той самий результат.

Однак, цілком допустимо змінювати змінні та об'єкти, які ви тільки що створили всередині функції. Це нормально, оскільки жоден код поза функцією не дізнається про те, що це сталося.
Це називається “локальна мутація.”

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

Примітка: є єдине виняток із цього правила, подивіться (99).

50. Виявлення нечистих обчислень за допомогою StrictMode.

Якщо ви в strict mode, кожен компонент буде рендеритись двічі. Це зроблено для того, щоб виявити, якщо ви нечемний хлопець.

51. Не все в React є чистим 😉

Я відкрию вам маленький секрет: всі ми, розробники на React, робимо нечисті речі постійно. І нам це подобається!

Хоча функціональне програмування сильно покладається на чистоту, в якийсь момент, десь, щось повинно змінюватися. Це, власне, і є сенсом програмування!

Тому у нас є свої маленькі секретні місця в React, де ми можемо робити все, що хочемо.
Ми робимо речі по-іншому "з боку". Хі-хі-хі!

В React побічні ефекти зазвичай належать до "обробників подій" (event handlers), в яких ви вільні змінювати зовнішні змінні, встановлювати стан (state) і робити все, що хочете. Хоча обробники подій визначені всередині вашого компонента, вони не виконуються під час рендерингу, тому їм не потрібно бути чистими!

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

Розділ 4: Додавання інтерактивності

52. Вони дають посилання на статтю в Medium про дизайн-системи.

Вони справді це роблять.
Ось він:

[

Все, що потрібно знати про дизайн-системи

→ Для французької версії, ось тут

uxdesign.cc

](https://uxdesign.cc/everything-you-need-to-know-about-design-systems-54b109851969?source=post_page-----1c65570e785d--------------------------------)

Вітаємо Audrey Hacq, ти справді потрапила в документацію React. Не можу дочекатися екранізації твоєї статті.

53. Вони кажуть, що об'єкти подій зазвичай називаються "e" за умовчанням.

Поганий хід, React Docs, поганий хід.

Я вже багато років борюся з розробниками, щоб ті писали це як "event".

Що мені тепер робити, React? Скажи мені, що, чорт візьми, я повинен робити з усіма цими "e" в моєму ідеальному коді?
Прослуховувачі подій (Event handlers) це просто функції, які ви передаєте як пропси.

Немає нічого особливого в них.

За винятком того, що вони призначені для реагування на взаємодії, такі як кліки, наведення курсору тощо.

Це означає, що кожен прослуховувач подій (Event handler), в самому низу дерева компонентів React, потраплятиме на один із вбудованих обробників, таких як "onClick" на елементі HTML, наприклад, "button".

55. Ви повинні називати прослуховувачі подій (Event handlers) на основі специфічних для додатка концепцій.

Коли ваш компонент підтримує кілька взаємодій, ви можете називати пропси для прослуховувачів подій (Event handlers) відповідно до специфічних для додатка концепцій. Наприклад “onPlayMovie” та “onUploadImage”.

56.

Документація нагадує, що існують захоплення (capture) та поширення (propagation) подій.

Якщо вам пощастило працювати з високорівневими речами і покладатися виключно на прослуховувачі подій (Event handlers), документація React нагадує вам, що повний пекел дом-поширення подій все ще існує. Нехай Бог допоможе нам усім.

57. e.stopPropagation() зупиняє поширення події.

Так, зупиняє. Він запобігає події досягати батьківських компонентів.

58. e.preventDefault() запобігає за замовчуванням.

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

59.

Локальні змінні не зберігаються між рендерингами.

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

Під “локальними змінними” я маю на увазі такі, як let index = 0 в тілі компонента.

60. Зміни в локальних змінних не викликають повторний рендер.

Лише кілька речей викликають повторний рендер. Зміна локальних змінних не є однією з них. Ви думали, що це Svelte?

61. Щоб оновити компонент з новими даними, потрібно зробити дві речі.

По-перше, ми повинні зберегти нові дані між рендерами. По-друге, потрібно викликати React для рендерингу компонента з новими даними (повторний рендер).

Ось тут і приходить на допомогу хук useState.
Це надає дві речі:

  • Змінну стану для збереження даних між рендерами.
  • Функцію оновлення стану (також звану “функцією set” або “set функцією”), щоб оновити змінну і викликати React для повторного рендеру компонента.

Чи можна зробити це простіше? Звісно, але це не Svelte. Це React, ми працюємо близько до "металу" і нам це подобається. Без магії в моєму JavaScript.

62. Стан є приватним для кожної інстанції компонента на екрані.

Стан не прив’язаний до конкретної функції компонента. Якщо ви рендерите один і той самий компонент в двох місцях на екрані, кожна копія отримає свій власний стан.

Це може здатися очевидним для досвідчених розробників, але на початку використання React це може вас заплутати. Особливо при використанні useState — це щось, що треба імпортувати, і імпорт виглядає загадково, бо хто знає, що там на іншому боці.
Тригери, рендеринг та комітинг.

У React процес оновлення компонента на екрані складається з трьох етапів:

Тригеринг рендеру — це постановка компонента в чергу для рендерингу, коли React не дуже зайнятий іншими справами, як, наприклад, витрачанням процесорних циклів.

Рендеринг — це виконання вашого компонента і отримання результату.

Комітинг — це виконання реальних змін, застосування результату рендерингу в жахливу машину браузерного DOM API за допомогою конструкцій, таких як метод Document.createElement().

63. Є дві причини, чому компонент може бути відрендерений.

  1. Це початковий рендер компонента.
  2. Було оновлено стан компонента (або одного з його предків).

Ось і все.

А як щодо зміни контексту? Ні, цей випадок вже покривається пунктом "було оновлено стан одного з його предків."
Є способи для підвищення продуктивності, але будьте обережні.

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

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

Основною технікою оптимізації, яку рекомендують у документації, є API “memo”. Воно дозволяє пропустити рендеринг компонентів, чиї вхідні дані не змінилися. Це безпечно, оскільки чисті функції завжди повертають однакові результати, тому їх можна кешувати.

Бачите? Ось чому вся ця нісенітниця з чистотою не є витратою часу!

65. React розумний.

Може, навіть розумніший за тебе!

Для повторних рендерів React застосовує мінімально необхідні операції (які обчислюються під час рендерингу!), щоб привести DOM у відповідність з останнім результатом рендерингу.

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

66.

React змінює DOM лише, якщо між рендерами є різниця.

Це, мабуть, одна з найкрутіших речей ever.

Дозвольте мені пояснити: навіть якщо компонент перерендерюється через зміни, React застосує зміни лише до тих DOM елементів, які змінилися!

Кожен незмінений DOM елемент у перерендереному компоненті залишиться в спокої! Ісусе Христе! Хто міг про це подумати? Ці люди з React дійсно мали великий мозковий момент!

67. Стан — це як знімок інтерфейсу користувача в часі.

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

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

На відміну від фотографії чи кадру з фільму, UI "знімок", який ви повертаєте, є інтерактивним. Він включає в себе логіку, таку як обробники подій (Event Handlers), що визначають, що відбувається у відповідь на введення.

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

68.

Значення змінної стану ніколи не змінюється під час рендеру.

Просто запам’ятайте цей принцип: "Значення змінної стану ніколи не змінюється під час рендеру."

Що б ви не робили, навіть якщо ви обгорнете console.log у три setTimeout, ви ніколи не втечете від вашого знімка рендеру. Оновлений стан — це привілегія, зарезервована для майбутніх рендерів. Покладіть край на всі сподівання.

69. React групує оновлення стану.

React чекає, поки весь код у вашому обробнику подій (Event Handler) не виконається, перед тим як обробляти оновлення стану. Це корисно, якщо ваш обробник подій встановлює кілька станів або змінює один і той самий стан кілька разів.

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

Подивімося на приклад обробника подій (Event Handler):

function handleClick() {  
 setCount(c => c + 1);  
 setFlag(f => !f);  
}

Замість того, щоб рендерити двічі, один раз для кожного оновлення стану, React рендерить лише один раз в кінці.

70. Для асинхронних обробників подій (Event Handlers) частина "async" може виконуватися в майбутньому пакеті.

Ми щойно сказали, що React чекає, поки весь код у ваших обробниках подій (Event Handlers) не виконається, перш ніж обробити оновлення стану. Але є виняток:

Коли React групує асинхронні обробники подій (Event Handlers), React не чекає на частину "async", а чекає тільки на сам обробник подій (Event Handler). Це означає, що якщо обробник подій встановлює стан після тайм-аутів, промісів або await, відкладені оновлення стану можуть виконатися в майбутньому пакеті.
Але оскільки обробники подій (Event Handlers) — це знімки часу, майбутній пакет оновлень також буде прив’язаний до стану, який існував на момент рендеру.

Подивимося на приклад:

async function handleClick() {  
 setPending(pending + 1); // пакет 1  
 setYolo(true); // пакет 1  
 await delay(3000);   
 setPending(pending - 1); // пакет 2  
 setCompleted(completed + 1); // пакет 2  
}

Тут оновлення стану до “await” виконуватимуться в одному пакеті, а оновлення стану після “await” — в майбутньому пакеті.

До виходу React 18 асинхронна частина взагалі не оброблялася в пакетах. Для історичної інформації про ці темні часи, перевірте це.
“Функції оновлення” (Updater functions) дозволяють встановлювати той самий стан неодноразово з обробника подій (Event Handler).

Це не найпоширеніший випадок, але якщо ви хочете оновити той самий стан кілька разів до наступного рендеру, замість того щоб передавати наступне значення, ви можете передати функцію оновлення, яка обчислює наступний стан на основі попереднього в черзі, наприклад, setNumber(n => n + 1).

Це спосіб сказати React “зробити щось зі значенням стану”, замість того щоб просто замінити його на конкретне значення.

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

Примітка автора: Смішно бачити, як “функції оновлення” (Updater functions) знижуються до “рідкісного випадку використання” в нових документах. Раніше вони були стандартним способом оновлення стану, якщо ви хотіли уникнути рідкісних помилок пакування.
Але, мабуть, зараз це вже не так.

72. Тримайте “функції оновлення” (Updater functions) чистими.

Я навіть не хочу знати, які дегенерати-розробники існують у світі. Але майте на увазі, якщо ви робите нечисті речі у функціях оновлення, StrictMode зловить вас.

73. Документація рекомендує правила найменування для функцій оновлення.

Документація рекомендує називати аргументи функції оновлення за першими літерами відповідної змінної стану:

setEnabled(e => !e);  
setLastName(ln => ln.reverse());

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

Мені подобається, як нові документи пояснюють, як реалізовані деякі внутрішні механізми React.

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

Внутрішня черга — це просто масив, що містить елементи, які були згруповані в пакет, і це можуть бути:

  1. Значення з регулярних оновлень, або
  2. “Функції оновлення” (updater functions).

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

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

Ось приклад того, що ви ніколи не повинні робити:

const [person, setPerson] = useState({  
 firstName: 'Barbara',  
 lastName: 'Hepworth',  
 email: '[email protected]'  
});  

function handleFirstNameChange(e) {  
 person.firstName = e.target.value; // \<-- ніколи не робіть цього  
}

Що ви робите? Хіба не бачите функцію setPerson (setPerson) там зверху?

Є дві проблеми з цим кодом:

  1. Він не викликає оновлення стану, тому що не викликається функція set (set function).
  2. Об'єкти є змінними в JavaScript, тому насправді цей код змінює стан у попередньому "знімку" рендеру.
    Це схоже на спробу змінити замовлення після того, як ви вже поїли. Крім того, це ускладнює налагодження та оптимізації React.

Спробуйте ось так:

const [person, setPerson] = useState({  
 firstName: 'Barbara',  
 lastName: 'Hepworth',  
 email: '[email protected]'  
});  

function handleFirstNameChange(e) {  
 setPerson({  
 ...person, // Копіюємо старі поля  
 firstName: e.target.value // Але переозначаємо тільки це поле  
 });  
}

Тобто, ви повинні створити новий об'єкт і передати його функції для оновлення стану.

76. Ви можете створювати нові об'єкти за допомогою синтаксису spread.

Як показано вище, документація рекомендує використовувати старий добрий синтаксис spread (…) для створення нового об'єкта на основі існуючого стану.

setPerson({  
 ...person, // Копіюємо старі поля  
 firstName: e.target.value // Але переозначаємо тільки це поле  
});

Будьте обережні з вкладеними об'єктами в стані.

Однак, будьте обережні: spread-операція є "поверхневою" — вона копіює лише один рівень.

Якщо ви хочете працювати з [вкладеними об'єктами в стані](https://beta.reactjs.org/learn/updating-objects-in-state#updating-a-nested-object), вам потрібно буде застосувати spread на кожному рівні і створити нові об'єкти для кожного рівня вкладеності.

## 78. Об'єкти не є "справжніми" вкладеними в JavaScript.

З усією цією розмовою про spread та вкладеність, [документація](https://beta.reactjs.org/learn/updating-objects-in-state#objects-are-not-really-nested) мудро нагадує нам, що об'єкти насправді не є вкладеними одне в одного, вони просто посилаються один на одного.

Насправді, можуть бути всілякі кругові та психоделічні перехресні посилання, які повністю зламають метафору вкладеності. Лишилось лише спостерігати в розпачі, як всі структури нашої західної цивілізації руйнуються!
Документація [рекомендує Immer](https://beta.reactjs.org/learn/updating-objects-in-state#write-concise-update-logic-with-immer), якщо вам не подобається занадто багато spread-операцій.

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

const [person, updatePerson] = useImmer({
name: "Michel",
age: 33
});

updatePerson(draft => {
draft.name = 'Sebastian';
});
```

Але Immer, завдяки магії JavaScript Proxies, створює новий об'єкт у фоновому режимі.

Документація React навіть пропонує спеціальний обгортка use-immer, в якому ви імпортуєте useImmer замість useState.

80. Тримайте масиви в стані як лише для читання.

Так само, як з об'єктами, коли ви хочете оновити масив, що зберігається в стані, вам потрібно створити новий (або зробити копію наявного).

81.

Документація надає таблицю методів, яких варто уникати для масивів в стані.

Ось вона:

pic

Звісно, якщо ви використовуєте Immer, всі методи доступні.

Особисто я ніколи не використовую "concat". Мені подобається використовувати spread.

Я не люблю використовувати "slice", тому що завжди плутаю його з його злим двійником "splice". Але іноді, коли вам потрібно вставити елемент у масив у певній позиції, це єдиний спосіб. На щастя, Github Copilot може заощадити мені поїздку на StackOverflow, коли ця ситуація виникає.

82. Документація нагадує, що масиви можуть містити об'єкти.

Іноді створення нових масивів недостатньо.
Якщо вони містять об'єкти, вам слід також подбати про створення нових об'єктів при оновленні стану.

Знову ж таки, Immer може бути корисним, особливо якщо у вас є дуже вкладені структури даних.

83. Думайте як дизайнер при управлінні своїм станом.

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

Тому вам варто думати на більш високому рівні. Замість того щоб зосереджуватись на дрібних поведінках кнопок та div, ви повинні думати про різні стани, які може мати ваш компонент, такі як “loading” (завантаження), “initial” (початковий), “error” (помилка), “success” (успіх). І які дії викликають перехід між ними.

Тримайте в голові “машини станів” (state machines) та “макети дизайнерів” (designer mockups), коли визначаєте свій стан, і React використає це, щоб зберегти складність на низькому рівні.

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

84. Декларативна логіка може використовувати більше рядків коду, ніж імперативна.

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

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

85. Документація згадує корисність інструментів на кшталт Storybook.

Хоча інструмент Storybook не згадується прямо, документація каже, що якщо компонент має багато візуальних станів, може бути зручно показати їх усі на одній сторінці.
І ці сторінки часто називаються «живими стилістичними посібниками» або «storybooks».

Ця частина документації припускає, що вам не обов'язково потрібен зовнішній інструмент для створення «storybook» — вони можуть бути просто частиною вашого основного додатку.

86. Групуйте пов'язані стани.

Просто зробіть це.

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

87.

Уникайте суперечностей у стані.

Документація радить проектувати ваш стан так, щоб «неможливі» або «суперечливі» стани не могли виникнути.

Наприклад, замість того, щоб мати два стани isSending і isSent, що можуть привести до суперечливого випадку, коли обидва будуть істинними, якщо якийсь відволіканий розробник забуде правильно оновити їх, ви повинні мати єдиний стан status з можливими значеннями sending і sent.

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

const isSending = status === 'sending';  
const isSent = status === 'sent';

Але це не змінні стану, тому вони ніколи не будуть поза синхронізацією.

88. Не відображайте пропси у стані.

Просто не треба. Дві причини:

  1. Навіщо це взагалі робити?
    2.
    ## 89. Уникайте глибоко вкладеного стану.

Документація рекомендує мати «плоский» або «нормалізований» стан замість глибоко вкладеного. Це досягається шляхом використання кількох ідентифікаторів (id) та масивів цих ідентифікаторів, ніби ми створюємо таблицю бази даних.

Мене тішить, що цей підхід все ще рекомендується. Це наче вчора, коли Дан Абрамов з епохи Redux навчив нас, нецивілізованих фронтенд-розробників, як робити дещо більш «бекендовим» способом.

90.

Уникайте глибоко вкладеного стану — додатковий рада!

Іноді можна зменшити вкладеність стану, перемістивши частину вкладеного стану в дочірні компоненти.

91. Пояснення "контрольованих" та "неконтрольованих" компонентів.

Міф спростовано. Опис цих термінів настільки чіткий, що я просто скопіюю з документації:

Зазвичай компонент з локальним станом називають “неконтрольованим”. […]

Водночас, можна сказати, що компонент є “контрольованим”, коли важлива інформація в ньому визначається через пропси, а не через власний локальний стан. Це дозволяє батьківському компоненту повністю визначати його поведінку. […]

На практиці, “контрольовані” та “неконтрольовані” компоненти не є суворими технічними термінами — кожен компонент зазвичай має певне поєднання локального стану та пропсів.

92. Існує таке поняття, як CSSOM.

Документація, намагаючись довести, що браузери використовують багато дерев для структурування UI, розповідає нам, що існує не тільки DOM, але й CSSOM (CSS Object Model)! І навіть є дерево доступності в браузері!

93. У React є внутрішнє “дерево UI”.

Не повинно бути сюрпризом, що React також використовує внутрішнє дерево для структурування UI:

pic

94. Стан прив’язаний до позиції в дереві UI.

Трохи спрощено сказати, що “стан живе всередині компонента”, адже компонент може бути відображений у більше ніж одній позиції в дереві UI, кожен з яких має свій ізольований стан.

Тому, більш точно, можна сказати, що стан прив’язаний до компонента в конкретній позиції в дереві UI.

95. React буде зберігати стан, поки ви відображаєте той самий компонент на тій самій позиції.

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

96. Насправді, стан всього піддерева зникає повністю.

Пам'ятайте, що важливою є саме позиція в дереві UI, а не в розмітці JSX, для React! Якщо ваш компонент умовно повертає різні теги JSX, які в результаті призводять до того самого компонента на тій самій позиції, React збереже стан (навіть якщо пропси зміняться)!

Розглянемо приклад:

if (isFancy) {  
 return (  
   \    \    )   
} else {    
 return (    
   \    \    \    )   
}  

Ви могли б очікувати, що внутрішній стан компонента Counter скидається, коли умова змінюється, але це не так! Ось хороше правило: якщо ви хочете зберегти стан між перерендереннями, структура вашого дерева повинна “відповідати” з одного рендеру на інший.

97. Скидання стану на тій самій позиції: хитрий спосіб.

Іноді ви дійсно хочете, щоб компонент скидав стан.

97. Наприклад, уявімо, що у вас є компонент Counter, який показує рахунок гравця:

{isPlayerA ? (  
  \  
) : (  
  \  
)}

Тут є баг: коли умова змінюється, рахунок залишається.

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

{isPlayerA &&  
  \  
}  
{!isPlayerA &&  
  \  
}

Це працює, тому що null є валідним елементом в дереві UI. React розпізнає цю конструкцію як дві незалежні позиції.

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

98.

98. Скидання стану в тій самій позиції: спосіб "Чада".

Досвідчені розробники React знають, що легітимний і універсальний спосіб скинути стан в тій самій позиції — це передавати унікальний ключ.

Ви, можливо, бачили використання key при рендерингу списків. Ви думаєте, що це все? Підготовтесь до шокуючого відкриття.

Ключі не тільки для списків! Ви можете використовувати ключі, щоб React відрізняв будь-які сусідні компоненти.

За замовчуванням, React використовує порядок у батьківському компоненті ("перший лічильник", "другий лічильник") для відрізнення компонентів. Але ключі дозволяють сказати React, що це не просто перший лічильник чи другий лічильник, а конкретний лічильник — наприклад, лічильник Тейлора:

{isPlayerA ? (  
  \  
) : (  
  \  
)}

Тут стан скидатиметься, коли гравець змінюється.

Не забувайте, що ключі не обов'язково повинні бути глобально унікальними.

99. А що, якщо ви хочете відновити старий стан при поверненні?

Ви думаєте, що React зробить усе за вас? Ні, не зробить!

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

Якщо ви хочете це, вам потрібно написати реальний код і використовувати перевірені патерни, такі як "підйом стану" (lifting state up), або localStorage, або приховувати елементи за допомогою CSS.

100. Найгірший патерн у React™️ — Коригування стану у відповідь на рендеринг.

Готуйтеся.

100. З усієї гидоти у світі React, нічого не порівняється з цим.

Зазвичай ви оновлюєте стан у обробниках подій (Event Handlers). Однак, у рідкісних випадках, ви можете захотіти налаштувати стан у відповідь на рендеринг — наприклад, ви можете захотіти змінити змінну стану, коли змінюється пропс.

Це зрозуміло.

101. У більшості випадків вам це не потрібно:

  • Якщо значення, яке вам потрібно, можна повністю обчислити з поточних пропсів (props) або іншого стану, то ви можете повністю позбутися від надлишкового стану.
  • Якщо ви хочете скинути стан всього дерева компонентів, передайте вашому компоненту інший ключ, як ми щойно бачили.
  • Якщо можете, оновлюйте весь відповідний стан в обробниках подій (Event Handlers).

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

Щоб вирішити цю проблему, досвідчені розробники React знають, що робити: оновлюйте стан в ефекті (Effect).

Ні! Нові документи чітко кажуть, що ефект (Effect) не підходить для цього, тому що він викликає повторний рендер дерева дочірніх компонентів.

102. І так, ви хочете уникати зайвих рендерів, але за яку ціну?

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

function List({ items }) {  
  const [selection, setSelection] = useState(null);  

  // Коригуємо стан під час рендеру  
  const [prevItems, setPrevItems] = useState(items);  
  if (items !== prevItems) {  
    setPrevItems(items);  
    setSelection(null);  
  }  
  // ...


Жах! Так, він змінює стан у функції рендеру! Це порушує, мабуть, найважливіше правило React: [Функція рендеру повинна бути чистою (pure).](https://beta.reactjs.org/learn/keeping-components-pure#purity-components-as-formulas)

Дозвольте [документам](https://beta.reactjs.org/reference/react/useState#storing-information-from-previous-renders) вибачитись за себе і дати вам всі моторошні деталі:

> Ви можете оновлювати стан лише _поточних компонентів_, що рендеряться таким чином. Виклик функції `set` для _іншого_ компонента під час рендеру є помилкою. Цей спеціальний випадок не означає, що можна порушувати інші правила чистих функцій. […]  
> 
> Цей шаблон може бути важким для розуміння і зазвичай краще його уникати.

[…]
> Решта вашої функції компонента все ще виконується (і результат буде відкинутий), але якщо ваша умова знаходиться після всіх викликів до хуків, ви можете додати ранній `return;` всередині неї, щоб перезапустити рендеринг раніше.

Так, вони навіть підкреслюють цю ідею, змушуючи мене уявити _ранній return_ всередині if-умови, що _встановлює стан \*здригаюсь\*_ у функції рендеру.

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

Щоб бути чесним, цей шаблон існує з того часу, як [хуки були вперше випущені](https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops).
Але, ймовірно, це буде трохи більше заохочуватися в майбутньому, враховуючи поновлений інтерес команди React до [зменшення небажаного використання ефектів](https://beta.reactjs.org/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes).

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

## 6: Редуктори

## 100.
Іноді [редуктори](https://beta.reactjs.org/learn/reacting-to-input-with-state#eliminating-impossible-states-with-a-reducer) можуть допомогти змоделювати складний стан.

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

Також ваші обробники подій стають більш лаконічними, оскільки вони лише вказують на "дії" користувача.

## 101. [Кожна дія редуктора повинна описувати одну взаємодію користувача.](https://beta.reactjs.org/learn/extracting-state-logic-into-a-reducer#step-1-move-from-setting-state-to-dispatching-actions)

Управління станом за допомогою редукторів трохи відрізняється від безпосереднього встановлення стану.
Документація рекомендує, щоб замість того, щоб говорити React, "що робити" через встановлення стану, ви вказували "що тільки що зробив користувач", відправляючи "дії" з ваших обробників подій (Event Handlers).

Кожна дія повинна описувати одну взаємодію користувача, навіть якщо це призводить до кількох змін у даних. Більш логічно відправити одну дію `reset_form`, ніж п'ять окремих дій `set_field`.

Логіка оновлення стану буде знаходитися в редукторі.

## 102. [Конвенції](https://beta.reactjs.org/learn/extracting-state-logic-into-a-reducer#step-1-move-from-setting-state-to-dispatching-actions) для об'єктів дій редуктора.

Об'єкт дії може мати будь-яку структуру. За умовчанням, зазвичай надається рядок `type`, який описує, що сталося, а додаткову інформацію передають через інші поля.

dispatch({
type: 'what_happened',
// інші поля тут
});

Помістіть ваш редуктор поза компонентом.

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

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

104.

За замовчуванням в редукторах використовуються оператори "switch".

Звісно, ви також можете використовувати оператори if, але це буде дещо примітивно.

Якщо ви використовуєте "switch", документація рекомендує обгортати кожен блок case в фігурні дужки { і }, щоб змінні, оголошені в різних case, не конфліктували між собою. Крім того, case зазвичай має закінчуватись командою return. Якщо ви забудете додати return, код "проскочить" до наступного case.

105. Немає задовільного пояснення, чому редуктори називаються редукторами.

Документація все ж дає сумну спробу пояснення:

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

Ла ді факін да.

106.

Використання редукторів — це питання особистих уподобань.

Це приємно, що документація каже, що редуктори можуть не підходити всім.

Рекомендуємо використовувати редуктор, якщо ви часто стикаєтеся з багами через неправильне оновлення стану в деякому компоненті і хочете внести більше структури в його код. Ви не повинні використовувати редуктори для всього: комбінуйте та вибирайте, що вам зручно! Ви навіть можете використовувати useState і useReducer в одному компоненті.

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

Автор цієї статті вважає, що редуктори — це добре, але вони є артефактом хайпу Redux в минулому, в поєднанні з необхідністю мати надійну альтернативу useState, коли хуки тільки почали з'являтися, оскільки були баги через відсутність пакетного оновлення. Господи, помилуй!

107.

Редуктори повинні бути чистими.

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

108. Контекст дозволяє передавати інформацію без «проп-дрилінгу»

Я радий повідомити, що нові документи React використовують фразу «проп-дрилінг», яка вже стала популярною в інших фреймворках, таких як Vue. Прокачай мої пропи, тату!

Передача пропсів — це чудовий спосіб явно передавати дані через ваше дерево UI до компонентів, які їх використовують.

Але передача пропсів може стати занадто громіздкою та незручною, коли вам потрібно передати пропс глибоко через дерево або якщо багато компонентів потребують однаковий пропс.
Найближчий спільний предок може бути досить далеко від компонентів, яким потрібні дані, а піднімання стану на таку висоту може призвести до ситуації, яку іноді називають «проп-дрилінг».

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

109. Контекст — це так просто, як один-два-три!

Використовувати контекст просто, достатньо виконати ці кроки:

  1. Створіть контекст. (За допомогою createContext.)
  2. Використовуйте цей контекст у компоненті, якому потрібні дані. (За допомогою useContext.)
  3. Надайте цей контекст з компонента, що вказує дані.
    (Обгортаючи їхніх нащадків у провайдері контексту ``) (Якщо ви не надасте контекст, React використовуватиме значення за замовчуванням для контексту.)

Мене трохи сумує, що при роботі з контекстами є три елементи для обробки. Для порівняння, версії контексту в Vue та Svelte вимагають лише два. Якщо ви знаєте, чому у React та Solid є додатковий крок, повідомте мене в коментарях!

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

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

111.

Ви можете використовувати та надавати один і той самий контекст з одного компонента.

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

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

Один компонент може використовувати або надавати багато різних контекстів без проблем.

112. Ви можете уявити контекст як спадкування властивостей CSS.

В CSS ви можете задати color: blue для елемента <div>, і будь-який DOM-елемент всередині нього, незалежно від того, як глибоко він знаходиться, успадкує цей колір, якщо інший DOM-елемент посередині не перезаписує його через color: green.
Аналогічно, в React єдиний спосіб перезаписати контекст, що йде зверху, — це обгорнути дочірні компоненти в провайдер контексту з іншою значенням.

Різні властивості, як-от color та background-color, не перезаписують одна одну. Подібно, різні контексти React не перезаписують один одного.

113. Не зловживайте Context!

Стоп! Співпрацюйте і слухайте. Документація попереджає вас про спокуси використання контексту.

Перед тим, як звертатися до контексту, пам'ятайте, що трохи "prop drilling" не є таким уже поганим. Це навіть може дати вам відчуття, що потік даних більш чіткий.

Також варто врахувати, що, можливо, причина вашого глибокого "drilling" полягає в тому, що ви ще не виділили в окремі компоненти ті візуальні частини, які все одно не роблять нічого з даними. Можливо, їм просто варто передавати пропс children замість цього.
Документація наводить загальні випадки використання контексту.

  • Темізація (наприклад, темна тема).
  • Поточний увійшовший користувач.
  • Маршрутизація (більшість рішень для маршрутизації використовують контекст для зберігання поточного маршруту. Саме так кожне посилання "знає", чи воно активне чи ні).
  • Управління великою кількістю станів.

115. Пам’ятайте, що контекст — це не лише для статичних значень.

Якщо ви передаєте інше значення при наступному рендері, React оновить кожен компонент, який його зчитує, нижче за деревом!

116. Контекст + Редуктор = Чадний Розробник.

Чи маєте ви все необхідне, щоб використовувати найкрутіший патерн у всьому React? Тоді приготуйтесь. Ми більше не в Redux.

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

117. Ще крутіше: Мати два контексти для вашого редуктора.

Щоб вивести цей патерн на новий рівень, ви повинні мати два контексти для вашого редуктора: один для стану, а інший для диспетчера (або для ваших власних обробників подій (Event Handlers)). Йоло!

Кожен компонент зчитує лише той контекст, який йому потрібен.

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

118.

Гіга-чад: Створюйте кастомні хуки для своїх контекстів.

Щось подібне до цього:

export function useTasks() {  
 return useContext(TasksContext);  
}  

export function useTasksDispatch() {  
 return useContext(TasksDispatchContext);  
}

Тепер ви можете використовувати контекст без необхідності імпортувати і сам контекст, і useContext. Один імпорт — і все!

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

119.

Думайте про рефи як про «таємну кишеню».

Давайте подивимося, як додати реф до вашого компонента:

const ref = useRef(0);

useRef повертає об'єкт, схожий на цей:

{  
 current: 0 // Значення, яке ви передали в useRef  
}

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

pic

Ілюстрація Рейчел Лі Наборс

Реф — це як «таємна кишеня» на вашому компоненті, яку React не відслідковує.

Реф — це звичайний об'єкт JavaScript, який ви не можете змінювати (незмінний). Насправді, useRef завжди повертає той самий об'єкт рефу. Він має властивість current, яку можна читати та змінювати (змінювана).

На наведеній ілюстрації властивість current зображена як стрілка, тому що, як властивість об'єкта, вона може «вказувати» на будь-що, що ви хочете відслідковувати.
(Також, з технічної точки зору, властивості об'єктів JavaScript реалізовані в підлягаючому C++ середовищі як «вказівники» (pointers), тому метафора зі стрілкою є дуже доречною.)

Ви не можете змінити кишеню і не можете змінити стрілку «current». Але ви можете змінити, на що вказує стрілка.

120. Коли використовувати рефи.

Звісно, таємні кишені — це круто, але чому ви взагалі повинні їх використовувати?

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

Але ось у чому різниця: Встановлення стейту призводить до перерендерингу компонента.
Зміна рефа не призводить до рендерингу!

Отже, ось правило, коли використовувати рефи:

  • Коли інформація використовується для рендерингу, тримайте її в стейті.
  • Коли інформація не використовується для рендерингу (можливо, її потрібно лише для обробників подій або викликів useEffect), і зміна її не вимагає перерендерингу, використання рефа може бути більш ефективним.

Зазвичай ви використовуєте реф, коли ваш компонент потребує «вийти за межі» React і взаємодіяти з зовнішніми API — часто з API браузера, яке не впливає на вигляд компонента. Звичні приклади цього:

  • Зберігання ID таймаутів (для setTimeout або setInterval)
  • Зберігання DOM елементів.

121. Ви не повинні читати (або записувати) значення ref.current під час рендерингу.

Звісно, ref.current є змінним.
Але якщо ви намагаєтесь взаємодіяти з ним під час рендерингу, ви робите щось неправильно.

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

Оскільки React не знає, коли змінюється ref.current, навіть читання цього значення під час рендерингу ускладнює прогнозування поведінки вашого компонента.

(Єдиним винятком є код на зразок if (!ref.current) ref.current = new Thing(), який встановлює реф лише один раз під час першого рендеру. Але цей патерн вже трохи натягнутий, оскільки зазвичай рефи ініціалізуються під час виклику useRef().)

122. Ви можете поєднувати рефи та стейт в одному компоненті.

Звісно, можете. Чому б і ні?
Як працює useRef всередині?

Приготуйтеся до того, що ваш мозок буде вражений. Ось вам пояснення.

Документація пояснює, що, за принципом, useRef можна було б реалізувати на основі useState. Ви можете уявити, що всередині React, useRef реалізовано ось так:

 // Всередині React  
 function useRef(initialValue) {  
   const [ref, unused] = useState({ current: initialValue });  
   return ref;  
}

Зверніть увагу, що setter для стейту не використовується в цьому прикладі. Це зайве, оскільки useRef завжди має повертати той самий об'єкт.

React надає вбудовану версію useRef, оскільки це достатньо поширений випадок.

І також тому, що реалізація useState вище працює лише порушуючи догму «стейт є лише для читання», що є святотатством! React, в своїй милості, надає нам useRef, що є святим способом мати змінний стейт, який незалежний від «односпрямованого потоку даних».
Коли ви змінюєте ref.current, це відбувається відразу.

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

ref.current = 5;  
console.log(ref.current); // 5

Це відбувається тому, що сам реф є звичайним об'єктом JavaScript, і тому поводиться як звичайний об'єкт.
Що щодо використання змінної поза компонентом замість рефів?

Уважні читачі можуть подумати: "Почекайте, рефи — це змінні дані, які не використовуються для рендерингу, то чому я маю використовувати рефи? Чому б не зберігати дані в змінній поза компонентом?"

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

Використання рефів працює добре для багатьох компонентів, тому що, як і стейт, рефи прив'язані до компонента в певній позиції в дереві UI.
Рефи допомагають вам отримати останній стан з асинхронної операції.

У документації згадується дуже поширений патерн.

Стан працює як моментальний знімок, тому ви не можете отримати останній стан з асинхронної операції, як, наприклад, тайм-аут. Однак ви можете зберігати останній стан у рефі! Реф є змінним, тому ви можете читати властивість current в будь-який час.

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

const [text, setText] = useState('');  
const textRef = useRef(text);  

function handleChange(e) {  
 setText(e.target.value);  
 textRef.current = e.target.value;  
}

[React](https://beta.reactjs.org/learn/manipulating-the-dom-with-refs) хоче, щоб ви тримали свої руки подалі від DOM.

Занадто багато кухарів зіпсують бульйон. Основна мета використання React полягає в тому, щоб він обробляв складні моменти роботи з DOM.

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

## 128. Якщо вам справді потрібен DOM елемент, використовуйте реф.

Ось як це зробити:

const myRef = useRef(null);
// ...
```

Якщо ви виконаєте ці кроки правильно, React автоматично помістить посилання на цей елемент в myRef.current. Потім ви можете робити все, що хочете з DOM елементом, наприклад, викликати myRef.current.scrollIntoView(). Як завжди, тримайте такі брудні побічні ефекти на обробниках подій (Event Listener) або в викликах useEffect.
Як управляти невідомою кількістю рефів DOM елементів?

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

Тоді пора взятися за більш складні рішення. Є два способи:

  1. Отримати один реф до батьківського елемента, а потім використовувати методи маніпуляції з DOM, такі як querySelectorAll. Це трохи "хакерське", але цілком легітимне!
  2. Більш "реактивне" рішення полягає в передачі функції атрибуту реф, що називається “реф колбеком” (ref callback). Коротко кажучи, вам потрібно вручну реалізувати ту магію, яку React робить для вас у попередньому випадку. Повні деталі тут.

130.

Використовуйте ‘forwardRef’, щоб отримати доступ до DOM елементів іншого компонента.

Досі ми працювали з атрибутами рефів на вбудованих компонентах для DOM елементів. Але якщо ви спробуєте поставити реф на ваш власний компонент, магія зникає, і за замовчуванням ви отримаєте null.

Що ви очікували? React не знає, як саме ви плануєте використовувати ваш власний компонент.

Компоненти, які хочуть надавати доступ до своїх DOM елементів, повинні явно дозволити це таким чином:

const MyInput = forwardRef((props, ref) => {  
  return <input ref={ref} {...props} />;
});

Звичайно, компонент може передати отриманий реф будь-якому внутрішньому компоненту, який йому потрібен.

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

131.

Чи можна використовувати ‘forwardRef’, щоб передавати кілька рефів до DOM елементів?

Це не пояснюється в документації, але я зацікавився цим питанням і знайшов корисний посібник на StackOverflow.

Це вимагає використання хука “useImperativeHandle”, який я зараз поясню.

132.

‘useImperativeHandle’ дозволяє вам контролювати, яку функціональність ви надаєте через ‘forwardRef’.

Так, “useImperativeHandle” призначений для використання разом з “forwardRef”, коли ви хочете зробити якісь кастомні трюки.

Або ви можете використати його, щоб обмежити те, що батьківський компонент може робити з наданим DOM елементом, ось так:

const MyInput = forwardRef((props, ref) => {  
 const realInputRef = useRef(null);  
 useImperativeHandle(ref, () => ({  
 // Надати доступ тільки до фокусу і нічого іншого  
 focus() {  
 realInputRef.current.focus();  
 },  
 }));  
 return <input ref={realInputRef} {...props} />;  
});

Це незвичайний випадок використання, тому не турбуйтеся надто про цей хук.

133.

Уникайте зміни DOM елементів, якими керує React.

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

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

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

134. Ефекти — це передостаннє поняття, яке вводиться в документації.

Коли хуки React були вперше випущені в лютому 2019 року, багато уваги було приділено хуку useEffect.
Це мало сенс на той момент, тому що useEffect був точкою контакту між новим підходом хуків і знайомим поняттям “життєвого циклу компонента” (component lifecycle).

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

Розмістивши ефекти в кінці документації, команда React прагне виправити ситуацію та зменшити надмірне використання та ідеалізацію ефектів.

135.

Є різниця між “ефектом” (Effect) і “побічним ефектом” (side effect).

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

У новій документації те, що ви додаєте до компонента за допомогою хука useEffect, називається “ефект” (Effect — завжди з великої літери).

Капіталізуючи слово “ефект”, легше відрізнити його від загального поняття “побічного ефекту” (side effect).

Це на відміну від старої документації, де те, що ви додаєте до компонента за допомогою хука useEffect, називалось “побічним ефектом” (side effect) або просто “ефектом” (effect).

Стара документація могла створити враження, що побічні ефекти можна знайти лише у хуку useEffect. Але це зовсім не так. Більшість побічних ефектів у React повинні бути в обробниках подій (event handlers) замість цього.

136.

Ефекти — це побічні ефекти (side effects), що викликаються рендерингом, а не діями користувача.

Нагадаємо, що в компонентах React є два типи коду:

  • Код рендерингу (який є чистим, за винятком одного крайнього випадку в пункті 99), і
  • побічні ефекти (side effects), які фактично щось роблять.

Найчастіше, як ми вже сказали, ви повинні використовувати обробники подій (event handlers), коли хочете створювати побічні ефекти.

Але іноді немає підходящої події від користувача. Ось тут і приходять на допомогу ефекти.

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

pic

Не на зображенні: Чистота нашого Господа Ісуса Христа.

137.

Не поспішайте додавати ефекти (Effects) до ваших компонентів.

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

Але чому добре уникати ефектів? Просто кажучи, вони занадто потужні і часто створюють код, який важко зрозуміти.

У певному сенсі, ефекти — це виконання угоди між фреймворком React і розробником: ми отримуємо всю силу JavaScript для впливу на роботу React, але ми повинні виявляти стриманість, щоб не бути поглинутими Левіафаном складності.

Думайте про ефекти як про останній ресурс, "шлях втечі".

138. Ефекти використовуються для синхронізації компонента з зовнішньою системою.

Як ми вже говорили раніше, ви вільно можете використовувати ефект (Effect) кожного разу, коли вам потрібен побічний ефект (side effect), який викликається рендерингом компонента.
Йдіть вперед, я вас не зупиняю.

Але згідно з новою документацією, є тільки одна причина, чому вам може знадобитися побічний ефект (side effect), викликаний рендерингом компонента.

І ця причина: Коли вам потрібно синхронізувати ваш компонент із зовнішньою системою.

Тут зовнішня система означає будь-який код, який не контролюється React, наприклад:

  • API DOM. Як таймери, керовані за допомогою setInterval(), або підписки на події за допомогою window.addEventListener().
  • Бібліотеки сторонніх розробників. Як бібліотека для анімацій з API типу animation.start() і animation.reset().
  • Мережа. Використання API fetch() для виконання мережевого запиту.

Примітка: Те, що ваш компонент взаємодіє з зовнішньою системою, ще не є достатньою причиною для використання ефекту. Якщо взаємодія із зовнішньою системою може бути оброблена за допомогою refs і обробників подій (event handlers) замість цього, будь ласка, робіть це.

139.

Ефекти виконуються після рендерингу.

Ефекти виконуються наприкінці процесу рендерингу, після того як браузер відобразить оновлення екрану. Іншими словами, useEffect "затримує" виконання певного коду до того, як рендер буде відображено на екрані.

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

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

140.

Як написати ефект.

Це легко. Просто імпортуйте і оголосіть свій ефект:

import { useEffect } from 'react';  

function MyComponent() {  
 useEffect(() => {  
 // Код тут виконається після *кожного* рендеру  
 });  
 return \;
}  

Коли ви синхронізуєте компонент із зовнішньою системою, ви можете використовувати пропс для визначення поведінки синхронізації. Давайте подивимось на приклад цього.
Ось компонент VideoPlayer, який синхронізується з браузерним медіа API, базуючись на пропсі "isPlaying":

function VideoPlayer({ src, isPlaying }) {  
 const ref = useRef(null);  

 useEffect(() => {  
 if (isPlaying) {  
 ref.current.play();  
 } else {  
 ref.current.pause();  
 }  
 });  
 return \;
}

Ось що відбувається, коли VideoPlayer рендериться (як при першому рендері, так і при повторному):

  • Спочатку React оновить екран.
  • Потім React виконає ваш ефект, який викликає play() або pause(), залежно від значення пропса isPlaying.

Ви можете використовувати подібний підхід для обгортання будь-якого не-React коду (наприклад, плагінів jQuery) в декларативні компоненти React.

141. Ефекти виконуються після кожного рендеру, якщо ви не оголосите залежності.

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

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

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

Почніть з додавання порожнього масиву:

useEffect(() => {  
 // ...  
 }, []);

Тоді ви побачите, що лінтер кричить про відсутність залежностей. Але не панікуйте! Просто слідуйте інструкціям, поки ваш ефект не виглядатиме так:

useEffect(() => {  
 if (isPlaying) { // Він використовується тут...  
 // ...  
 } else {  
 // ...


 }, [isPlaying]); // ...тому він повинен бути оголошений тут!

Ось і все, ви закінчили! Вказуючи [isPlaying] як масив залежностей, ви говорите React, що він повинен пропустити повторне виконання вашого ефекту, якщо значення isPlaying залишиться таким самим, як під час попереднього рендеру.

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

142. В React залежності обирають вас.

Зверніть увагу, що ви не можете "обирати" свої залежності.

Як розробник React, ваша задача — написати код ефекту.
Ваш єдиний вибір стосовно залежностей:

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

Саме так, масив залежностей повинен відповідати тому, що очікує React. Ви не повинні мати впливу на це питання. Така поведінка допомагає виявляти багато помилок у вашому коді.

Уважні читачі можуть запитати: Як React визначає, що повинно бути в масиві залежностей?

Технічно, масив залежностей — це список усіх реактивних значень, на які є посилання в ефекті. Ми розглянемо, що таке "реактивне значення", пізніше.

Читачі також можуть запитати: Хіба я не повинен мати змоги змінювати масив залежностей, щоб точно визначити, коли повинен виконуватись ефект?

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

143. Різні поведінки з масивом залежностей та без нього.

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

useEffect(() => {  
 // Цей ефект виконується після кожного рендеру  
});  
useEffect(() => {  
 // Цей ефект виконується тільки при монтуванні (коли компонент з'являється)  
}, []);  
useEffect(() => {  
 // Цей ефект виконується при монтуванні *і також* якщо a або b змінились з останнього рендеру  
}, [a, b]);

144. Refs і функції set опускаються з масиву залежностей.

В прикладі з VideoPlayer ви могли помітити, що ефект використовує "ref", але React не хоче включати "ref" як залежність.

Це тому, що об'єкт "ref" має стабільну ідентичність.
Він ніколи не змінюється, тому не буде сам по собі спричиняти повторне виконання ефекту.

Функції set, що повертаються хоком useState, також мають стабільну ідентичність, тому вони також будуть опущені.

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

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

145.

Додайте функцію очищення, якщо це необхідно.

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

Для цього потрібно повернути функцію очищення з вашого ефекту:

useEffect(() =\> {  
 const connection = createConnection();  
 connection.connect();  
 return () =\> {  
 connection.disconnect();  
 };  
}, []);

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

Деякі ефекти не потребують функції очищення. Частіше вони потребують — але якщо ні, React поводитиметься так, наче ви повернули порожню функцію очищення, яка нічого не робить.

146.

Ефекти виконуються двічі під час розробки.

У часи Першої світової війни Швейцарія мала маленьку постійну армію, але народ був добре навченим у стрільбі з гвинтівки. У 1912 році кайзер Вільгельм II запитав, що зробить міліція з чверті мільйона швейцарців, якщо він вторгнеться з армією півмільйона німців. Їхня відповідь? "Стріляти двічі і йти додому."

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

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

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

Зазвичай відповідь полягає у впровадженні функції очищення. Функція очищення повинна зупинити або скасувати те, що ефект робив раніше.

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

Якщо ваш ефект запускає анімацію, функція очищення повинна скинути анімацію до початкових значень:

useEffect(() =\> {  
 const node = ref.current;  
 node.style.opacity = 1; // Запускаємо анімацію  
 return () =\> {  
 node.style.opacity = 0; // Скидаємо до початкового значення  
 };  
}, []);

Під час розробки значення opacity буде спочатку встановлено в 1, потім у 0, а потім знову в 1. Це має мати такий самий видимий для користувача ефект, як і безпосереднє встановлення в 1, що відбудеться в продакшн-версії.
Як очистити ефект, який отримує дані?

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

useEffect(() =\> {  
 let ignore = false;  
 async function startFetching() {  
 const json = await fetchTodos(userId);  
 if (!ignore) {  
 setTodos(json);  
 }  
 }  
 startFetching();  
 return () =\> {  
 ignore = true;  
 };  
}, [userId]);

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

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

Чому? Документація наводить кілька причин:

  • Ефекти не виконуються на сервері, тому складно змусити отримання даних через ефекти працювати разом з серверним рендерингом.
  • Легко опинитися в ситуації "мережевих водоспадів", і важко підтримувати паралельне отримання даних.
  • Важко реалізувати попереднє завантаження або кешування.
  • Для того, щоб уникнути станів гонки, потрібно писати чимало додаткового коду.

Замість цього документація пропонує два альтернативні варіанти:

  • Якщо ви використовуєте мета-фреймворк (наприклад, Next.js, Gatsby або Remix), використовуйте вбудований механізм отримання даних.
  • Якщо ні, розгляньте використання клієнтського кешу. Популярні рішення з відкритим кодом включають TanStack Query, useSWR та React Router 6.4+.
    Придбання продукту — це не ефект.

Іноді, навіть якщо ви напишете функцію очищення, неможливо уникнути видимих для користувача наслідків виконання ефекту двічі:

useEffect(() => {  
 // Неправильно: цей ефект спрацьовує двічі в режимі розробки, виявляючи проблему в коді.  
 fetch('/api/buy', { method: 'POST' });  
}, []);

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

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

Зверніть увагу, що POST-запити можна використовувати в ефектах, якщо вони не пов'язані з подіями користувача, а належать до рендерингу компонента (наприклад, POST-запит для відстеження аналітики).

149. Кожен рендер має свої ефекти.

Ви можете розглядати useEffect як "прикріплення" частини поведінки до результату рендеру.

У React "рендеринг" — це як знімок інтерфейсу користувача в часі (наприклад, знімок стану).
Пропси компонента, стан, локальні змінні, обробники подій (Event Handlers) та Ефекти обчислюються та приєднуються до результату рендеру завдяки магії замикань.

Нові документи пропонують два глибоких аналізи того, як це працює для Ефектів.

150.

Вам не потрібен Ефект для кешування дорогих обчислень.

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

Якщо ви визначите його залежності, він буде виконуватись тільки тоді, коли вони зміняться.

Пам’ятайте, що те, що ви кладете всередину useMemo, повинно бути чистим.

І як завжди, не оптимізуйте занадто рано. Нові документи мають гарний посібник для визначення, чи є обчислення дорогим. Як правило, якщо обчислення займає 1 мілісекунду або більше, воно може виграти від використання useMemo.

151.

Уникайте ланцюгів Ефектів.

Іноді вам може захотітися створити ланцюжок Ефектів, де кожен з них налаштовує частину стану на основі іншого стану та викликає один одного.

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

Як правило: перш ніж створювати ланцюг Ефектів, спробуйте обчислити, що ви можете під час рендеру, і коригуйте стан якомога більше в обробнику подій (Event Listener).

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

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

152. Будьте обережні при ініціалізації додатка за допомогою Effects.

Деяка логіка повинна виконуватися лише один раз під час завантаження додатка.

Ви можете розмістити її в Effect (ефекті) на верхньому рівні компонента. Але це не найкращий варіант! Ви швидко зрозумієте, що він виконується двічі в режимі розробки.

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

let didInit = false;  

function App() {  
 useEffect(() => {  
 if (!didInit) {  
 didInit = true;  
 // Виконується лише один раз при завантаженні додатка  
 loadDataFromLocalStorage();  
 checkAuthToken();  
 }  
 }, []);  
 // ...

Або ось так:

if (typeof window !== 'undefined') { // Перевірка, чи працюємо в браузері.
// Виконується лише один раз при завантаженні додатка
checkAuthToken();
loadDataFromLocalStorage();
}

function App() {
// ...
}
```

Перевірте документацію для порівняння варіантів.

153. Якщо ваша зовнішня система — це “store” (сховище), розгляньте можливість використання useSyncExternalStore.

Якщо ви використовуєте Effect (ефект), щоб синхронізувати ваш компонент з чимось, що можна описати як “зовнішнє сховище” (external store), на яке ви “підписуєтесь” — скажімо, щось на кшталт Redux або навіть деякі вбудовані браузерні API — ви можете використати useSyncExternalStore.

Тепер буду чесним. Мені не зовсім зрозуміло, чому нова документація так активно пропагує цей незрозумілий новий хук.
Мені не зовсім зрозуміло, чому це краще за useEffect (useEffect). Також в мене склалося враження, що useSyncExternalStore призначений для авторів бібліотек.

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

154. Життєвий цикл Effect (ефекту) відрізняється від життєвого циклу компонента.

Кожен компонент React проходить однаковий життєвий цикл:

  • Компонент монтується (mount), коли його додають на екран.
  • Компонент оновлюється (updates), коли він отримує нові пропси (props) або стан (state).
    Це зазвичай відбувається у відповідь на якусь взаємодію.
  • Компонент демонтується (unmount), коли його видаляють з екрану.

Це гарний спосіб думати про компоненти, але не про Effects (ефекти).

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

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

Тепер ви думаєте з допомогою Effects (ефектів)!

155.

Кожен Effect (ефект) представляє окремий процес синхронізації.

Спробуйте встояти перед спокусою додавати непов'язану логіку до вашого Effect (ефекту) лише тому, що ця логіка повинна виконуватися в той самий час, що й ефект, який ви вже написали.

Легше підтримувати два Effect (ефекти), якщо вони представляють окремі процеси.

156. Реактивні значення: Інший погляд на залежності.

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

Отже, що таке реактивні значення?

Пропси (props), стан (state) і всі змінні, оголошені всередині компонента, є реактивними (reactive), оскільки вони обчислюються під час рендеру і беруть участь у потоці даних React.

Будь-яке реактивне значення може змінюватися при повторному рендері, тому реактивні значення, які використовуються в Effect (ефекті), повинні бути включені до масиву залежностей (dependencies array).

Effects (ефекти) можуть реагувати на будь-які значення з тіла компонента. (Скидання титулу!)

Effects (ефекти) також є реактивними блоками коду. Вони повторно синхронізуються, коли значення, які ви читаєте всередині них, змінюються.

Отже, якщо ваш Effect (ефект) використовує значення, яке визначене поза компонентом, лінтер не попросить вас додавати його як залежність.

157.

Змінні, що можуть змінюватися (mutable values), включаючи глобальні змінні, не можуть бути залежностями.

Змінна, як-от location.pathname, не може бути залежністю. Вона змінюється, тому може змінюватися в будь-який момент, повністю поза потоком рендерингу React. Це також порушує правила React, оскільки читання змінних даних під час рендерингу (коли ви обчислюєте залежності) порушує чистоту рендеру.

З тієї ж причини, змінні, як-от ref.current, або те, що ви читаєте з них, також не можуть бути залежністю.

158. Що робити, коли ви не хочете повторно синхронізувати?

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

Ви можете “довести” лінтеру, що ці значення не є реактивними значеннями.
Наприклад, перемістивши їх поза компонент або всередину Effect (ефекту).

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

159. Не ігноруйте лінтер.

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

useEffect(() => {  
 // ...  
 // eslint-disable-next-line react-hooks/exhaustive-dependencies  
}, []);

Цього не потрібно робити. Лінтер — ваш друг. Якщо лінтер пропонує залежність, але додавання її спричиняє зациклення або інші проблеми, це не означає, що лінтер слід ігнорувати.
Це означає, що вам потрібно змінити код всередині (або поза) Effect (ефекту) так, щоб це значення не було реактивним і не потрібно було його додавати до залежностей.

160. Останнє і найпотужніше: useEffectEvent

Давайте завершимо наш тур по Effects (ефектах) з вибухом!

Протягом багатьох років кодові бази React були "пошкоджені" через //eslint-disable-next-line як спосіб вирішення проблем з Effect (ефектами). Ми, розробники React, чекали на useEffectEvent з лютого 2019 року!

Я не можу передати словами свою радість. Дозвольте коду говорити за себе:

function Page({ url }) {  
 const { items } = useContext(ShoppingCartContext);  
 const numberOfItems = items.length;  

 const onVisit = useEffectEvent(visitedUrl => {  
 logVisit(visitedUrl, numberOfItems);  
 });  

 useEffect(() => {  
 onVisit(url);  
 }, [url]); // Усі залежності оголошені  

 // ...  
}

Тут onVisit — це Effect Event.
Код всередині нього не є реактивним. Ось чому ви можете використовувати numberOfItems (або будь-яке інше реактивне значення!) без хвилювань, що це спричинить повторне виконання навколишнього коду при змінах!

Зверніть увагу, що ця функція досі є експериментальною. Команда React натякала, що вона буде випущена, коли нова документація React вийде з бета-версії, але цього поки що не сталося. Також у майбутньому React 19 цієї функції не буде.

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

Заключні думки

Люди, дозвольте сказати вам щось, коли я тільки починав свою програмістську кар'єру, я б ніколи не подумав, що документація React буде такою хорошою.

Вона завжди була хорошою, але ніколи не такою хорошою.

Я пам'ятаю, коли єдиний спосіб дізнатися про React був поїздки на ці шикарні конференції та спілкування з класними хакерами. Ти міг вважати себе щасливчиком, якщо вони не зламали твій Palm Pilot на помсту.

Тому я закликаю вас, молодих і старих, знайти час у вашому дні і прочитати нову документацію React.

І, можливо, просто можливо, ви зрозумієте, що комп'ютерне програмування — це та діяльність, яка принесе мир і процвітання нашому світу.

Але найголовніше, ви отримаєте задоволення. І в цьому вся суть.
Нехай сила буде з вами.

Якщо ви готові до виклику, переходьте до останньої теми в документації, “Custom Hooks” (Користувацькі хуки).

Дякую за прочитання! Якщо вам подобаються гумористичні технічні історії і ви хочете підтримати мене, щоб я міг писати вічно, подумайте про підписку на Medium. Це коштує $5 на місяць і дає вам безмежний доступ до статей на Medium. Якщо ви підпишетесь за моїм посиланням, я отримаю невелику комісію. Ви також можете підписатися на мене в Medium і Twitter.

[

Приєднуйтесь до Medium за моїм реферальним посиланням - Себастьян Карлос

Читайте всі статті від Себастьяна Карлоса (і тисяч інших авторів на Medium).

Ваш внесок на членство напряму…

sebastiancarlos.medium.com

Перекладено з: React JS Best Practices From The New Docs

Leave a Reply

Your email address will not be published. Required fields are marked *