Изучение lazy () и ‹Suspense /› в контексте параллельного React
В фреймворке React появятся несколько важных новых функций и улучшений; интересно для нас, разработчиков, полезно для конечного пользователя. В этой статье мы рассмотрим использование lazy()
и <Suspense />
, но для того, чтобы эти функции были полностью полезными, нам также необходимо понимать параллельный React и преимущества его поддержки для многопоточности.
Начнем с того, что означают эти термины:
- Concurrent React: обновление базовой платформы React, которое позволяет ему одновременно работать над несколькими задачами (отрисовками). Мало того, эти задачи могут переключаться между ними в соответствии с их приоритетом в режиме реального времени.
lazy()
: lazy - это новый API в React, который очень легко помогает в разделении кода и импорте компонентов в ваши скрипты.<Suspense />:
Suspense похож на ловушку ошибок, которая позволяет нам определять резервный JSX, если часть содержимого, которое он упаковывает, не загружена. Если вы думаете о блоке try catch, блок catch - это наш запасной вариант Suspense, а все, что находится внутри<Suspense></Suspense>
, - это наш блок try. Этот пример кажется немного надуманным, поскольку блоки catch обычно не должны выполнять бизнес-логику, но основной процесс очень похож.
Приостановка также позволяет нам определить порог, чтобы не показывать счетчик в случае, если компонент не загрузился в течение определенного периода времени. Подумайте об использовании приложений с высокой скоростью Интернета, которые мигают в счетчике на долю секунды перед отображением загруженного содержимого. Мы можем предотвратить это с помощью запасных вариантов.
Можем ли мы использовать эти функции сейчас?
lazy () и ‹Suspense /›: Да - 16,6
Эти 2 функции доступны для интеграции прямо сейчас и были выпущены с обновлением React 16.6. Однако с версией 16.6 мы можем использовать их только в стандартном синхронном React. Concurrent React еще не включен в окончательный общедоступный выпуск, но доступен в альфа-сборке 16.7.
Параллельный режим: Да - 16,7 альфа
Как упоминалось выше, нам нужно собрать React16.7 alpha, чтобы тестировать и использовать React в параллельном режиме.
Это не проблема для целей тестирования; используйте следующую команду NPM для установки этой версии для react
и react-dom
:
npm install [email protected]
npm install [email protected]
Итак, отличная новость заключается в том, что мы можем сразу же начать играть с параллельным React. Ниже мы рассмотрим, как использовать параллельный React, и перейдем к примерам lazy()
и <Suspense />
.
Примечание. Несмотря на то, что эти функции - и другие, анонсированные на React Conf 18 - предлагают несколько отличных новых способов работы с React, я бы не рекомендовал вам переходить на альфа-версию и просто заменить текущую кодовую базу пока что. Альфа-сборки обычно содержат экспериментальные или незавершенные модули, которые после окончательного выпуска могут измениться по функциям или API. Вместо этого продублируйте существующие проекты для тестирования одновременного поведения вашего приложения.
Пример с ленивым ()
Как упоминалось выше, lazy()
позволяет нам выполнять разделение кода в нашем приложении и очень легко импортировать компоненты.
(Разделение кода. Идея разбить (или разделить) ваше приложение на несколько частей, чтобы не загружать сразу все приложение. Возможно, вы захотите загрузить только домашнюю страницу, например, а позже загрузите другую часть вашего приложения, когда пользователь действительно ее посещает. Или, поскольку React теперь асинхронный, вы можете захотеть загрузить контент непосредственно перед посещением пользователя!)
Допустим, я хочу загрузить <Product />
интерфейс, отвечающий за отображение страницы продукта. В этом нет необходимости, когда пользователь впервые посещает мой сайт, поэтому я хочу отделить это от моего основного пакета.
Давайте продолжим и импортируем этот компонент:
import React, { lazy } from 'react'; const Product = lazy(() => import('./ProductHandler'));
lazy
импортирован из стандартной библиотеки React. Это просто функция. Мы также передаем функцию в lazy()
как единственный аргумент. Эта функция должна возвращать динамический импорт, который возвращает обещание компоненту, содержащему компонент React, который мы хотим загрузить (который должен быть экспортом модуля по умолчанию).
Это довольно минимизированный код, и он не сильно отличается от того, к чему мы привыкли, если мы просто импортируем компонент без возможности разделения кода:
import { Product } from './ProductHandler';
Примечание. Стандартный оператор импорта без разделения кода!
Теперь у нас есть простой API для импорта компонентов через разделение кода, нам нужен способ их отображения, если они полностью загружены, и отображения заполнителя, если это не так. Вот где <Suspense />
вступает в игру.
Использование ‹Suspense /› для управления ленивым () импортом
Реализация Suspense также проста и не требует значительных изменений в вашей разметке JSX.
Импортируйте Suspense из стандартной библиотеки пакетов React с помощью следующего оператора импорта:
import React, { lazy, Suspense } from 'react';
Чтобы реализовать Suspense, оберните ваш JSX (с лениво загруженным импортированным компонентом внутри него) объектом <Suspense>
:
... render() { return( <div className='product-list'> <h1>My Awesome Product</h1> <Suspense fallback={<h2>Product list is loading...</h2>}> <p>Take a look at my product:</p> <section> <Product id='PDT-49-232' /> </section> </Suspense> </div> ); }
Здесь мы заключили наш лениво загруженный <Product />
в <Suspense>
.
Свойство Suspense fallback
позволяет нам передавать компонент (или любую разметку JSX) в Suspense для отображения нашего состояния загрузки. В приведенном выше примере я только что использовал подзаголовок <h2>
, но не стесняйтесь использовать свой счетчик.
Приостановка не зависит от дерева компонентов
Я намеренно поместил другую разметку JSX в Suspense, чтобы продемонстрировать, что Suspense не обязательно должен быть прямым родительским объектом наших отложенных компонентов.
Это также выгодно для нас, поскольку мы можем не захотеть отображать текст «Взгляните на мой продукт:» до загрузки <Product />
. Кроме того, <section>
предоставляет дополнительные отступы и стили, которые выглядели бы странно в выгруженном состоянии продукта.
Suspense обрабатывает дерево компонентов из медленно загруженного компонента.
Если мой компонент не загружен, Suspense работает от этого компонента вверх по дереву компонентов до тех пор, пока не будет найден объект Suspense.
Это означает, что мы можем иметь вложенные объекты Suspense в другие объекты Suspense. Рассмотрим следующую схему гнезда:
MainInterface Suspense lazy ProductComponent Suspense lazy ProductImageGalleryComponent Suspense lazy ProductReviewsComponent
В следующем сценарии я также лениво загружу интерфейс изображений продуктов и интерфейс обзоров продуктов. Последние 2 полностью независимы друг от друга и запускаются после загрузки моего компонента ProductComponent.
Когда это имеет смысл использовать? Если соблюдены 2 критерия:
- Когда интерфейсы изображений и обзоров содержат большую часть изолированного кода, который пользователь может не посещать. Если небольшой процент пользователей действительно посещает эти компоненты, не стоит загружать их при начальной загрузке приложения.
- Если компоненты галереи изображений и обзоров используются где-то еще в моем приложении. В любом случае я уже лениво загружаю страницу своего продукта, поэтому загрузка дополнительных компонентов на этой странице, которые используются только на моей странице продукта, может не иметь большого смысла. Конечно, все это зависит от размеров файлов компонентов.
Мы также можем обернуть несколько отложенно загружаемых компонентов в один объект Suspense.
Мы также можем настроить сценарий, в котором Suspense ожидает разрешения нескольких лениво загруженных компонентов:
... render() { return( <div className='product-list'> <h1>My Awesome Product</h1> <Suspense fallback={<h2>Product list is loading...</h2>}> <p>Take a look at my product:</p> <section> <Product id='PDT-49-232' /> <Product id='PDT-50-233' /> <Product id='PDT-51-234' /> </section> </Suspense> </div> ); }
В этом сценарии все мои продукты должны быть загружены до того, как Suspense перейдет с резервного на мой загруженный контент.
Также стоит отметить: ‹ErrorBoundary /›
То, что плохо документировано в Интернете, - это границы ошибок. Что такое границы ошибок?
Границы ошибок позволяют нам обнаруживать ошибки из любого места в их дереве компонентов, регистрировать ошибку и отображать резервный пользовательский интерфейс. Мы определяем ErrorBoundaries как отдельный компонент класса, определяем магические методы
getDerivedStateFromError
,componentDidCatch
иrender
и просто импортируем компонент и включаем его в наш метод рендеринга.
В случае, если динамический импорт не загружается, границы ошибок пригодятся для отображения более удобного пользовательского интерфейса (более того, чем бесконечный счетчик). Граница ошибок может уловить сетевую ошибку, срабатывающую, когда модуль не загружается, что, в свою очередь, может отображать пользовательский интерфейс, уведомляющий конечного пользователя, и может предоставлять кнопку для перезагрузки ресурса (или запускать автоматическую перезагрузку).
Дополнительную информацию о границах ошибок можно найти на сайте reactjs.org:
Как использовать Concurrent React: только ≥16,7 альфа
То, о чем мы говорили выше, можно реализовать в синхронном React, но что, если бы мы хотели воспользоваться преимуществами асинхронных возможностей параллельного React?
Мы меняем способ визуализации нашего корневого <App />
элемента.
Где мы это делаем в стандартном React:
ReactDOM.render(<App />, document.getElementById('root'));
Мы делаем это для параллельного React:
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
Это все, что нужно изменить, чтобы включить Concurrent React.
Продолжая разговор о Suspense, одна из вещей, которые он может делать в среде Concurrent, - это поддерживать загрузку пороговых значений счетчика.
Порог отката при задержке: maxDuration prop
Это еще не все. Как упоминалось выше, мы также можем определить откат, чтобы предотвратить отображение состояния загрузки в случае высокой скорости интернета. Просто включите опору <Suspense />
maxDuration
, которая принимает значение в миллисекундах.
Опять же, не ожидайте, что это сработает в несовместимом React.
Примечание. Если вы сейчас внедряете Suspense с React 16.6, подождите, пока 16.7 не будет в окончательной версии, прежде чем поддерживать пороговые значения. Чтобы убедиться, что вы не интегрируете ничего, что не поддерживается вашей текущей версией, запустите сборку разработки с React.StrictMode.
React.StrictMode
Если вы разрабатываете React 16.6, рекомендуется обернуть <React.StrictMode>
вокруг <App />
, чтобы любые неподдерживаемые функции, которые вы можете интегрировать, выводились в консоли разработки в виде предупреждений.
Оберните строгий режим вокруг вашего приложения следующим образом:
ReactDOM.render(<React.StrictMode>
<App /></React.StrictMode>
, document.getElementById('root'));
Заключение
Порог задержки maxDuration
- лишь одно из преимуществ использования Concurrent React.
Concurrent React обещает быть высокой производительностью и обеспечивать плавный UX, что в первую очередь влечет за собой меньшее количество переходов при загрузке или извлечении контента. Все эти функции являются добровольными, что означает, что разработчикам не нужно принимать какие-либо из них, и их внедрение не нарушит вашу текущую кодовую базу.
Выпуская lazy()
и <Suspense />
, мы лишь поверхностно видим то, что будет реализовано в асинхронном фреймворке React - мы, безусловно, можем ожидать больших изменений в пакетах, приложениях и пользовательском опыте в ближайшие месяцы.
Разработчики ядра React также работают над экспериментальными пакетами, такими как react-cache
, который имеет возможность кэшировать импортированные и извлеченные данные, и поэтому им не нужно запрашивать их повторно при каждом посещении или обновлении страницы. В настоящее время это недоступно для публичного тестирования, но может быть еще одним ценным инструментом для дополнительной упрощения пользовательского интерфейса для конечного пользователя.