Як оновитися до React 18

08 березня 2022 року від Ріка Хенлона


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

Будь ласка, повідомляйте про будь-які проблеми , з якими ви зіткнетеся під час оновлення до React 18.

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


Встановлення

Щоб встановити останню версію React:

npm install react react-dom

Або якщо ви використовуєте пряжу:

yarn add react react-dom

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

Під час першого встановлення React 18 ви побачите попередження в консолі:

ReactDOM.render більше не підтримується в React 18. Замість нього використовуйте createRoot. Поки ви не перейдете на новий API, ваш застосунок буде поводитися так, ніби він працює на React 17. Дізнайтеся більше: https://reactjs.org/link/switch-to-createroot

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

// Before
import { render } from 'react-dom';
const container = document.getElementById('app');
render(<App tab="home" />, container);

// After
import { createRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = createRoot(container); // createRoot(container!) if you use TypeScript
root.render(<App tab="home" />);

Ми також змінили unmountComponentAtNode на root.unmount:

// Before
unmountComponentAtNode(container);

// After
root.unmount();

Також вилучено зворотний виклик з рендерингу, оскільки він зазвичай не дає очікуваного результату при використанні Suspense:

// Before
const container = document.getElementById('app');
render(<App tab="home" />, container, () => {
  console.log('rendered');
});

// After
function AppWithCallbackAfterRender() {
  useEffect(() => {
    console.log('rendered');
  });

  return <App tab="home" />
}

const container = document.getElementById('app');
const root = createRoot(container);
root.render(<AppWithCallbackAfterRender />);

Не існує повної заміни старого API зворотного виклику рендерингу - це залежить від вашого випадку використання. Дивіться повідомлення робочої групи Заміна render на createRoot для отримання додаткової інформації.

Нарешті, якщо ваша програма використовує серверний рендеринг з гідратацією, оновіть hydrate до hydrateRoot:

// Before
import { hydrate } from 'react-dom';
const container = document.getElementById('app');
hydrate(<App tab="home" />, container);

// After
import { hydrateRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = hydrateRoot(container, <App tab="home" />);
// Unlike with createRoot, you don't need a separate root.render() call here.

Для отримання додаткової інформації дивіться обговорення Робочої групи тут.

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

Оновлення API серверного рендерингу

У цьому релізі ми оновлюємо наші react-dom/server API для повної підтримки Suspense на сервері та потокового SSR. У рамках цих змін ми оголошуємо старий потоковий API Node, який не підтримує інкрементне потокове передавання Suspense на сервері, застарілим.

Використання цього API тепер буде попереджувати:

  • renderToNodeStream: Deprecated ⛔️️

Натомість, для потокового передавання у середовищі Node використовуйте:

  • renderToPipeableStream: Новий ✨
  • .

Ми також представляємо новий API для підтримки потокового SSR з Suspense для сучасних периферійних середовищ виконання, таких як Deno та Cloudflare:

  • renderToReadableStream: Новий ✨
  • .

Наступні API продовжать працювати, але з обмеженою підтримкою Suspense:

  • renderToString: Обмежено ⚠️
  • .
  • renderToStaticMarkup: Обмежено ⚠️

Нарешті, цей API продовжить працювати для рендерингу електронних листів:

  • renderToStaticNodeStream

Для отримання додаткової інформації про зміни в API рендерингу на сервері дивіться пост Робочої групи Оновлення до React 18 на сервері, глибоке занурення в нову архітектуру Suspense SSR та доповідь Shaundai Person's на тему Потоковий серверний рендеринг з Suspense на React Conf 2021.

Оновлення визначень TypeScript

Якщо ваш проект використовує TypeScript, вам потрібно оновити залежності @types/react та @types/react-dom до останніх версій. Нові типи є безпечнішими і виправляють проблеми, які раніше ігнорувалися перевіркою типів. Найпомітнішою зміною є те, що дочірній проп тепер потрібно вказувати явно при визначенні пропсів, наприклад:

interface MyButtonProps {
  color: string;
  children?: React.ReactNode;
}

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

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

Автоматичне пакетування

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

// Before React 18 only React events were batched

function handleClick() {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React will only re-render once at the end (that's batching!)
}

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

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

// After React 18 updates inside of timeouts, promises,
// native event handlers or any other event are batched.

function handleClick() {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React will only re-render once at the end (that's batching!)
}

setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React will only re-render once at the end (that's batching!)
}, 1000);

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

import { flushSync } from 'react-dom';

function handleClick() {
  flushSync(() => {
    setCounter(c => c + 1);
  });
  // React has updated the DOM by now
  flushSync(() => {
    setFlag(f => !f);
  });
  // React has updated the DOM by now
}

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

Нові API для бібліотек

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

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

    React 18 також представляє нові API для одночасного рендерингу, такі як startTransition, useDeferredValue та useId, про які ми розповідаємо у пості релізу.

    Оновлення у суворий режим

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

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

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

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

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

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

    * React mounts the component.
        * Layout effects are created.
        * Effect 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 effect setup code runs
        * Effect setup code runs

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

    Налаштування середовища тестування

    Під час першого оновлення тестів для використання createRoot ви можете побачити таке попередження у консолі тестування:

    Поточне середовище тестування не налаштовано на підтримку act(...)

    Щоб виправити це, встановіть globalThis.IS_REACT_ACT_ENVIRONMENT на true перед запуском тесту:

    // In your test setup file
    globalThis.IS_REACT_ACT_ENVIRONMENT = true;

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

    Ви також можете встановити прапорець як false, щоб сказати React, що act не потрібен. Це може бути корисно для наскрізних тестів, які імітують повне середовище браузера.

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

    Більш детальну інформацію про тестування API та пов'язані з ним зміни можна знайти у робочій групі.

    Вилучення підтримки Internet Explorer

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

    Якщо вам потрібна підтримка Internet Explorer, рекомендуємо залишитись на React 17.

    Застаріле

    • react-dom: ReactDOM.render застаріла. Його використання попередить і запустить ваш застосунок у режимі React 17.
    • react-dom: ReactDOM.hydrate застаріла. Його використання призведе до попередження та запуску вашого застосунку в режимі React 17.
    • react-dom: ReactDOM.unmountComponentAtNode застаріло.
    • .
    • react-dom: ReactDOM.renderSubtreeIntoContainer застаріло.
    • react-dom/server: ReactDOMServer.renderToNodeStream застаріло.

    Інші руйнівні зміни

    • Послідовне використання Effect timing: React тепер завжди синхронно змиває функції ефектів, якщо оновлення було викликано під час дискретної події вводу користувача, наприклад, клацання або натискання клавіші. Раніше поведінка не завжди була передбачуваною або послідовною.
    • .
    • Суворіші помилки гідратації: Невідповідності гідратації через відсутній або зайвий текстовий вміст тепер обробляються як помилки, а не як попередження. React більше не намагатиметься "латати" окремі вузли, вставляючи або видаляючи вузол на клієнтській стороні, щоб відповідати розмітці сервера, і повернеться до клієнтського рендерингу до найближчої межі <Suspense> у дереві. Це гарантує узгодженість гідратованого дерева і дозволяє уникнути потенційних дірок у конфіденційності та безпеці, які можуть бути спричинені невідповідністю гідратації.
    • Дерева призупинення завжди узгоджені: Якщо компонент призупиняється до того, як його буде повністю додано до дерева, React не додасть його до дерева у незавершеному стані і не запустить його ефекти. Натомість React повністю викине нове дерево, дочекається завершення асинхронної операції, а потім повторить спробу рендерингу з нуля. React виконає повторну спробу рендерингу паралельно і без блокування браузера.
    • Ефекти компонування з підвішуванням: Коли дерево повторно призупиняється і повертається до резервного варіанту, React тепер очищає ефекти компонування, а потім створює їх заново, коли вміст всередині межі знову показується. Це виправляє проблему, яка заважала бібліотекам компонентів правильно вимірювати макет при використанні з Suspense.
    • Нові вимоги до середовища JS: React тепер залежить від можливостей сучасних браузерів, включаючи Promise, Symbol та Object.assign. Якщо ви підтримуєте старіші браузери та пристрої, такі як Internet Explorer, які не підтримують сучасні функції браузера за замовчуванням або мають невідповідні реалізації, розгляньте можливість додавання глобального заповнення у вашу програму, що постачається.

    Інші помітні зміни

    React

    • Компоненти тепер можуть повертати undefined: React більше не попереджує, якщо ви повертаєте undefined з компонента. Це робить дозволені значення, які повертає компонент, узгодженими зі значеннями, дозволеними всередині дерева компонентів. Ми рекомендуємо використовувати лінтер, щоб запобігти помилкам на кшталт забуття оператора return перед JSX.
    • У тестах попередження act тепер можна вмикати: Якщо ви виконуєте наскрізні тести, попередження act непотрібні. Ми запровадили механізм opt-in, щоб ви могли вмикати їх лише для модульних тестів, де вони є корисними та вигідними.
    • Немає попередження про setState у немонтованих компонентах: Раніше React попереджав про витік пам'яті при виклику setState у немонтованому компоненті. Це попередження було додано для підписок, але люди в основному стикаються з ним у сценаріях, де встановлений стан є нормальним, а обхідні шляхи роблять код гіршим. Ми видалили це попередження.
    • Немає придушення логів консолі: Коли ви використовуєте строгий режим, React рендерить кожен компонент двічі, щоб допомогти вам знайти неочікувані побічні ефекти. У React 17 ми придушили консольні логи для одного з двох рендерингів, щоб полегшити їх читання. У відповідь на відгуки спільноти про те, що це заплутує, ми прибрали придушення. Натомість, якщо у вас встановлено React DevTools, рендеринги другого логу будуть відображатися сірим кольором, і буде опція (за замовчуванням вимкнена), щоб повністю їх придушити.
    • Покращено використання пам'яті: React тепер очищає більше внутрішніх полів при демонтажі, що робить вплив невиправлених витоків пам'яті, які можуть існувати у коді вашої програми, менш серйозним.

    Сервер React DOM

    • renderToString: Більше не виникатиме помилок під час призупинення на сервері. Замість цього буде видано резервний HTML-код для найближчої межі <Suspense>, а потім буде спроба відрендерити той самий вміст на клієнтській стороні. Все одно рекомендується перейти на потокове API, наприклад, renderToPipeableStream або renderToReadableStream замість цього.
    • renderToStaticMarkup: Більше не виникатиме помилок під час призупинення на сервері. Замість цього буде видано резервний HTML-код для найближчої межі <Suspense>.

    Changelog

    Ви можете переглянути повний журнал змін тут.