React v18.0

29 березня 2022 року від The React Team


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


Наша остання велика версія містить такі стандартні покращення, як автоматичне пакетування, нові API, такі як startTransition, та потоковий серверний рендеринг із підтримкою Suspense.

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

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

Якщо ви пропустили, ми поділилися частиною цього бачення на React Conf 2021:

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

Для користувачів React Native, React 18 буде поставлятися в React Native з новою архітектурою React Native. Щоб дізнатися більше, дивіться доповідь React Conf тут.

Що таке паралельний React?

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

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

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

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

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

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

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

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

Поступове впровадження паралельних функцій

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

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

Загальна стратегія оновлення полягає в тому, щоб ваш застосунок працював на React 18 без зміни існуючого коду. Потім ви можете поступово почати додавати паралельні функції у власному темпі. Ви можете використовувати <StrictMode>, щоб допомогти виявити помилки, пов'язані з паралелізмом, під час розробки. Суворий режим не впливає на продуктивну поведінку, але під час розробки він буде реєструвати додаткові попередження та подвійні виклики функцій, які, як очікується, є недієздатними. Він не виловлює все, але ефективно запобігає найпоширенішим типам помилок.

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

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

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

Для отримання додаткової інформації дивіться наш попередній пост: Як оновитися до React 18.

Невизначеність у структурах даних

У React 18 ви можете почати використовувати Suspense для отримання даних у таких фреймворках як Relay, Next.js, Hydrogen або Remix. Спеціальне отримання даних за допомогою Suspense технічно можливе, але все ж не рекомендується як загальна стратегія.

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

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

Серверні компоненти все ще у розробці

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

Серверні компоненти все ще є експериментальними, але ми плануємо випустити початкову версію у молодшому релізі 18.x. Тим часом ми працюємо з такими фреймворками, як Next.js, Hydrogen і Remix, щоб просунути пропозицію і підготувати її до широкого впровадження.

Що нового в React 18

Нова можливість: Автоматичне пакетування

>.

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

// Before: only React events were batched.
setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React will render twice, once for each state update (no batching)
}, 1000);

// After: updates inside of timeouts, promises,
// native event handlers or any other event are batched.
setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React will only re-render once at the end (that's batching!)
}, 1000);

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

Нова можливість: Переходи

>Переходи

Перехід - це нове поняття у React для розрізнення термінових і нетермінових оновлень.

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

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

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

Зазвичай, для найкращого користувацького досвіду, єдине введення користувача повинно призводити як до термінового оновлення, так і до нетермінового. Ви можете використовувати API startTransition всередині події введення, щоб повідомити React, які оновлення є терміновими, а які "переходами":

import { startTransition } from 'react';

// Urgent: Show what was typed
setInputValue(input);

// Mark any state updates inside as transitions
startTransition(() => {
  // Transition: Show the results
  setSearchQuery(input);
});

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

  • useTransition: хук для запуску переходів, включаючи значення для відстеження стану очікування.
  • startTransition: метод для запуску переходів, коли не можна використовувати хук.

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

Дивіться документацію щодо переходів тут.

Нові можливості Suspence

Suspense дозволяє декларативно вказати стан завантаження для частини дерева компонентів, якщо вона ще не готова до відображення:

<Suspense fallback={<Spinner />}>
  <Comments />
</Suspense>

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

Ми представили обмежену версію Suspense кілька років тому. Однак єдиним підтримуваним варіантом використання було розбиття коду за допомогою React.lazy, а при рендерингу на сервері він взагалі не підтримувався.

У React 18 ми додали підтримку Suspense на сервері та розширили його можливості за допомогою функцій одночасного рендерингу.

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

Додатково дивіться RFC для Suspence у React 18.

Нові клієнтські та серверні API рендерингу

У цьому релізі ми скористалися можливістю переробити API, які ми надаємо для рендерингу на стороні клієнта та сервері. Ці зміни дозволяють користувачам продовжувати використовувати старі API в режимі React 17, поки вони переходять на нові API в React 18.

React DOM-клієнт

Ці нові API тепер експортуються з react-dom/client:

  • createRoot: Новий метод створення кореня для відтворення або розмонтування. Використовуйте його замість ReactDOM.render. Нові можливості в React 18 не працюють без нього.
  • hydrateRoot: Новий метод для гідратації програми, що рендериться на сервері. Використовуйте його замість ReactDOM.hydrate у поєднанні з новими API React DOM Server. Нові можливості в React 18 не працюють без нього.

І createRoot, і hydrateRoot приймають нову опцію onRecoverableError, якщо ви хочете отримувати сповіщення, коли React відновлюється після помилок під час рендерингу або гідратації для логування. За замовчуванням React буде використовувати reportError, або console.error у старих браузерах.

Документацію по React DOM Client дивіться тут.

Сервер React DOM

Ці нові API тепер експортуються з react-dom/server і мають повну підтримку потокового Suspense на сервері:

  • renderToPipeableStream: для потокового передавання у середовищі Node.
  • renderToReadableStream: для сучасних периферійних середовищ виконання, таких як Deno та Cloudflare workers.

Існуючий renderToString метод продовжує працювати, але не рекомендується до застосування.

Дивіться документацію до React DOM Server тут.

Нова поведінка у суворому режимі

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

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

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

До цієї зміни React монтував компонент і створював ефекти:

* React mounts the component.
  * Layout effects are created.
  * Effects are created.

У суворому режимі в React 18 React імітуватиме демонтаж та повторний монтаж компонента в режимі розробки:

* React mounts the component.
  * Layout effects are created.
  * Effects are created.
* React simulates unmounting the component.
  * Layout effects are destroyed.
  * Effects are destroyed.
* React simulates mounting the component with the previous state.
  * Layout effects are created.
  * Effects are created.

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

Нові хуки

useId

useId - новий хук для генерації унікальних ідентифікаторів як на клієнтській стороні, так і на сервері, уникаючи при цьому невідповідностей гідратації. Він насамперед корисний для бібліотек компонентів, що інтегруються з API доступності, які вимагають унікальних ідентифікаторів. Це вирішує проблему, яка вже існує в React 17 і нижче, але в React 18 вона ще більш важлива, оскільки новий потоковий серверний рендеринг надає HTML в неправильному порядку. Дивіться документацію тут.

Note

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

useTransition

useTransition і startTransition дозволяють позначити деякі оновлення стану як нетермінові. Інші оновлення стану вважаються терміновими за замовчуванням. React дозволить терміновим оновленням стану (наприклад, оновленням текстового введення) переривати нетермінові оновлення стану (наприклад, рендеринг списку результатів пошуку). Дивіться документацію тут

useDeferredValue

.

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

useSyncExternalStore

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

Note

useSyncExternalStore призначено для використання бібліотеками, а не кодом програми.

використовуйте ефект вставки

useInsertionEffect - новий хук, який дозволяє CSS-in-JS бібліотекам вирішувати проблеми продуктивності впровадження стилів при рендерингу. Якщо ви ще не створили бібліотеку CSS-in-JS, ми не очікуємо, що ви взагалі будете використовувати цей хук. Цей хук буде працювати після мутації DOM, але перед тим, як ефекти компонування прочитають новий макет. Це вирішує проблему, яка вже існує в React 17 і нижче, але є ще більш важливою в React 18, оскільки React поступається браузеру під час одночасного рендерингу, даючи йому можливість переобчислити макет. Дивіться документацію тут.

Note

useInsertionEffect призначений для використання бібліотеками, а не кодом програми.

Як оновити

Дивіться Як оновитися до React 18 для отримання покрокових інструкцій та повного списку порушень і важливих змін.

Changelog

React

  • Додано useTransition та useDeferredValue для відокремлення термінових оновлень від переходів. (#10426, #10715, #15593, #15272, #15578, #15769, #17058, #18796, #19121, #19703, #19719, #19724, #20672, #20976 by @acdlite, @lunaruan, @rickhanlonii та @sebmarkbage)
  • Додано useId для генерації унікальних ідентифікаторів. (#17322, #18576, #22644, #22672, #21260 за допомогою @acdlite, @lunaruan та @sebmarkbage)
  • Додайте useSyncExternalStore, щоб допомогти зовнішнім бібліотекам сховищ інтегруватися з React. (#15022, #18000, #18771, #22211, #22292, #22239, #22347, #23150 by @acdlite, @bvaughn, and @drarmstr)
  • Додано startTransition як версію useTransition без очікуваного відгуку. (#19696 від @rickhanlonii)
  • Додати useInsertionEffect для бібліотек CSS-in-JS. (#21913 by @rickhanlonii)
  • Змусити Suspense перемонтувати ефекти компонування, коли вміст з'являється знову. (#19322, #19374, #19523, #20625, #21079 за допомогою @acdlite, @bvaughn та @lunaruan)
  • Зробіть <StrictMode> повторний запуск ефектів, щоб перевірити можливість відновлення стану. (#19523 , #21418 за допомогою @bvaughn та @lunaruan)
  • Вважати, що символи завжди доступні. (#23348 від @sebmarkbage)
  • Видалити полізаповнення object-assign. (#23351 від @sebmarkbage)
  • Вилучено непідтримуваний unstable_changedBits API. (#20953 by @acdlite)
  • Дозволити компонентам рендерити невизначені. (#21869 від @rickhanlonii)
  • Синхронно змити useEffect, отриманий внаслідок дискретних подій, таких як кліки. (#21150 від @acdlite)
  • Призупинення fallback={undefined} тепер поводиться так само, як і null і не ігнорується. (#21854 від @rickhanlonii)
  • Вважати, що всі lazy() розв'язуються до одного еквівалента компонента. (#20357 від @sebmarkbage)
  • Не латати консоль під час першого рендерингу. (#22308 від @lunaruan)
  • Покращити використання пам'яті. (#21039 від @bgirard)
  • Покращити повідомлення у разі примусового викидання рядків (Temporal.*, Symbol тощо) (#22064 від @justingrant)
  • Використовувати setImmediate, якщо він доступний через MessageChannel. (#20834 від @gaearon)
  • Виправлено нерозповсюдження контексту всередині підвішених дерев. (#23095 by @gaearon)
  • Виправлено useReducer спостереження некоректних пропсів шляхом вилучення механізму eager bailout. (#22445 by @josephsavona)
  • Виправлено ігнорування setState у Safari при додаванні iframe. (#23111 від @gaearon)
  • Виправлено збій при рендерингу ZonedDateTime у дереві. (#20617 by @dimaqq)
  • Виправлено збій, коли у тестах документ встановлено значення null. (#22695 від @SimenB)
  • Виправлено помилку onLoad, яка не спрацьовує, коли увімкнено паралельні функції. (#23316 від @gnoff)
  • Виправлено попередження, коли селектор повертає NaN. (#23333 від @hachibeeDI)
  • Виправлено збій, коли у тестах документ встановлено у null. (#22695 від @SimenB)
  • Виправити згенерований заголовок ліцензії. (#23004 by @vitaliemiron)
  • Додати package.json як одну з точок входу. (#22954 by @Jack)
  • Дозволити підвішування за межами межі підвішування. (#23267 by @acdlite)
  • Записувати помилку, яку можна виправити, щоразу, коли гідратація завершується невдало. (#23319 by @acdlite)

React DOM

Сервер React DOM

  • Додано новий потоковий зображувач. (#14144, #20970, #21056, #21255, #21200, #21257, #21276, #22443, #22450, #23247, #24025, #24030 by @sebmarkbage)
  • Виправити постачальників контексту у SSR при обробці декількох запитів. (#23171 by @frandiox)
  • Повернення до клієнтського рендерингу у разі невідповідності тексту. (#23354 від @acdlite)
  • Зменшити renderToNodeStream. (#23359 від @sebmarkbage)
  • Виправлено хибний журнал помилок у новому відображенні сервера. (#24043 від @eps1lon)
  • Виправлено ваду у новому серверному рендері. (#22617 від @shuding)
  • Ігнорувати значення функцій та символів всередині користувацьких елементів на сервері. (#21157 від @sebmarkbage)

React DOM Test Utils

  • Викидати, коли act використовується у виробництві. (#21686 від @acdlite)
  • Підтримується вимкнення попереджень про хибні дії за допомогою global.IS_REACT_ACT_ENVIRONMENT. (#22561 від @acdlite)
  • Розширити попередження про дію на всі API, які можуть планувати роботу React. (#22607 від @acdlite)
  • Зробити act пакетні оновлення. (#21797 від @acdlite)
  • Видалити попередження для висячих пасивних ефектів. (#22609 від @acdlite)

Оновлення React

.
  • Відстежувати пізніше змонтовані корені у швидкому оновленні. (#22740 by @anc95)
  • Додано поле експорту до package.json. (#23087 by @otakustay)

Серверні компоненти (експериментальні)

  • Додати підтримку контексту сервера. (#23244 by @salazarm)
  • Додано підтримку лінивих . (#24068 by @gnoff)
  • Оновлення плагіна webpack для webpack 5 (#22739 від @michenly)
  • Виправлено помилку у завантажувачі вузлів. (#22537 від @btea)
  • Використовуйте globalThis замість window для крайових середовищ. (#22777 від @huozhi)