Оптимизация производительности интерфейса на основе React.Suspense и React.lazy

React.js

React 16.6 был выпущен в октябре 2018 года. Эта версия содержит множество новых функций и расширяет возможности React. Двумя наиболее заметными особенностями являютсяReact.Suspenseа такжеReact.lazy. Эти две функции выводят разделение кода React и отложенную загрузку на новый уровень. Используя эти две функции, вы можете загружать файлы компонентов только тогда, когда они действительно необходимы.

В этой статье в основном рассказывается, как я использую его в проекте.React.Suspenseа такжеReact.lazyИ преимущества, которые эта функция приносит нам, разработчикам React.

1. Зачем использовать разделение кода?

Благодаря постоянному развитию клиентских технологий и появлению новых технологий, таких как модули ES6, преобразование Babel и упаковка веб-пакетов, интерфейсные приложения теперь можно выполнять модульным способом, который легко поддерживать.

Обычно мы упаковываем все модули в один файл, который загружается, когда запрашивается веб-страница для отображения всего приложения. Однако с постоянным расширением функций веб-страницы возникают такие проблемы, как медленная загрузка веб-страницы и заикание взаимодействия, что очень ухудшает работу пользователя. Основная причина этой проблемы в том, что при загрузке страницы мы загружаем весь код сразу, будь то код, который используется в данный момент, или код, который будет использоваться позже. Но пользователи не будут использовать все функции при первом входе, поэтомуcode-splittingТо есть появился термин кодовое расщепление.

Как и webpack, он предоставляет функцию разделения кода. Определение разделения кода в webpack выглядит следующим образом:

Code splitting is one of the most compelling features of webpack. This feature allows you to split your code into various bundles which can then be loaded on demand or in parallel.

Это означает, что код можно разделить на несколько пакетов, которые можно загружать по требованию или параллельно. Поэтому, чтобы повысить производительность приложения, мы можем попытаться разумно разделить код и отложить загрузку.

2. Как разделить код

1. Динамическая загрузка

Стандарт ES6 ввел импорт для облегчения статической загрузки модулей. В виде:

 import xxx from xxx.js

Хотя импорт очень полезен для загрузки модулей, способ статической загрузки модулей в определенной степени ограничивает нас в достижении асинхронной загрузки модулей. Однако синтаксис import() для динамической загрузки модулей в настоящее время находится на стадии предложения, и webpack представил и использовал его. import() предоставляет API на основе Promise, поэтому возвращаемое значение import() представляет собой объект Promise с выполненным или отклоненным состоянием. В виде:

    import(/* webpackChunkName: 'module'*/ "module")

    .then(() => {

    //todo

    })

    .catch(_ => console.log('It is an error'))

скопировать код

Когда веб-пакет скомпилирован, он распознает синтаксис импорта динамической загрузки, и веб-пакет создаст отдельный пакет для текущего динамически загружаемого модуля. Если вы используете официальную структуру Create-react-app или серверную среду рендеринга React Next.js, вы можете напрямую использовать синтаксис динамического импорта. Если ваш скаффолдинг сконфигурирован самостоятельно через веб-пакет, вам необходимо следовать официальному руководству по его настройке, перейдите к [1].

2. Динамически загружать компоненты React

Одним из самых популярных методов является использованиеReact-loadable[2] Компоненты React с отложенной загрузкой, предоставляемые библиотекой. Он использует синтаксис import() для загрузки компонентов React с использованием синтаксиса Promise. В то же время React-loadable поддерживает рендеринг React на стороне сервера. Обычно мы реализуем компоненты следующим образом:

    import LazyComponet from 'LazyComponent';

    export default function DemoComponent() {

    return (

    <div>

    <p>demo component</p>

    <AComponent />

    </div>

    )

    }

скопировать код

В приведенном выше примере предположимLazyComponetсуществуетDemoComponentМы не показываем это при рендеринге. Но поскольку мы используем синтаксис импорта дляLazyComponetимпорт, так во время компиляции это будетLazyComponetкод сDemoComponentКод упакован в тот же пакет. Однако это не то, чего мы хотим. Итак, мы можем сделать это, используяReact-loadableленивая загрузкаLazyComponet, в то время какLazyComponetКод упакован отдельно в бандл. Мы можем посмотреть на примеры, представленные на официальном сайте:

    import Loadable from 'react-loadable';

    import Loading from './my-loading-component';

    const LoadableComponent = Loadable({

    loader: () => import('./my-component'),

    loading: Loading,

    });

    export default class App extends React.Component {

    render() {

    return <LoadableComponent/>;

    }

    }

скопировать код

Как видно из примера, react-loadable использует динамический метод import() и присваивает импортированный компонент свойству загрузчика. В то же время react-loadable предоставляет свойство загрузки для установки компонента, который будет отображаться при загрузке компонента.

3. Использование лени и неизвестности

React.lazy and Suspense is not yet available for server-side rendering. If you want to do code-splitting in a server rendered app, we recommend Loadable Components. It has a nice guide for bundle splitting with server-side rendering.

Прежде чем использовать его, нам нужно обратить особое внимание на то, что React официально его поддерживает, React.lazy и Suspense не поддерживают рендеринг на стороне сервера. Поэтому учащиеся, использующие рендеринг на стороне сервера, могут обратиться кreact-loadableа такжеloadable-components[3].

Поскольку я обновляю исходный проект, следующее содержание этой статьи в основном направлено на работу, проделанную старым проектом по обновлению последней версии React.

1. Обновление кода до последней версии React

Если ваш код — Reactv16, вы можете обновить его до последней версии напрямую.Конечно, React16.6 уже предоставляет ленивый и приостановочный методы. Если это версия до версии 16, выполните официальную операцию по миграции.

2. Определите компоненты ленивой загрузки исходного кода

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

    import LazyComponent from "../components/LazyComponent ";

скопировать код

изменить на:

    const LazyComponent = React.lazy(() =>

    import(/* webpackChunkName: 'lazyComponent'*/ "../components/LazyComponent")

    );

скопировать код

Как показано в коде: замените код, который статически ссылается на компонент, на вызов React.lazy(), передайте анонимную функцию в качестве параметра в lazy() и динамически введите ее в функцию.lazyComponentкомпоненты. Таким образом, браузер не загрузит компонент, пока мы его не отобразим.lazyComponent.bundle.jsФайл и его зависимости. Который, webpackChunkName в импорте бандла мы определяем как имя файла.

Что произойдет, если код, от которого зависит компонент, не был загружен, когда React хочет отобразить компонент?<React.Suspense/>появился, чтобы помочь нам решить проблему. Прежде чем код будет загружен, он отобразит значение, переданное в резервном свойстве. Итак, наш исходный код:

    return (

    <div>

    <MainComponet />

    <LazyComponent />

    </div>

    )

скопировать код

изменить на:

    return (

    <div>

    <MainComponet />

    <React.Suspense fallback={<div>正在加载中</div>}>

    <LazyComponent />

    </React.Suspense>

    </div>

    )

скопировать код

В качестве запасного варианта его можно изменить на любой счетчик, и на этот раз мы не будем слишком много оптимизировать. React выдаст вам ошибку, если вы не используете React.suspense, поэтому не забудьте использовать React.lazy с React.Suspense. На данный момент по сетевому запросу мы видим, что динамически загружаемый lazyComponent упакован в бандл отдельно, однако при загрузке первого экрана бандл уже загружен на нашу страницу, что может быть не тем, что нам нужно. , мы хотим перезагружаться, когда нам это нужно. Затем мы берем на себя управление и загружаем файл, когда он нам нужен.

3. Загрузка через переменное управление

Первоначально все пять компонентов с отложенной загрузкой, которые я выбрал, были компонентами эластичного слоя, поэтому необходимо установить состояние для управления отображением и скрытием компонента, поэтому мы изменили код на:

    return (

    <div>

    <MainComponet />

    {this.state.showLazyComponent && (

    <React.Suspense fallback={<div>正在加载中</div>}>

    <LazyComponent />

    </React.Suspense>

    )}

    </div>

    )

скопировать код

В результате при загрузке первого экрана наш компонент ленивой загрузки не загружаетсяLazyComponentСоответствующий комплект поставки. Страница не загрузит js, пока мы не нажмем на компонент, который нужно отобразить. Это достигает цели нашего разделения кода и ленивой загрузки. Итак, когда мы это делаем, был ли уменьшен объем основного пакета? Далее давайте посмотрим на файл пакета.

4. Файл пакета

Запакованные файлы до оптимизации:

Запакованные файлы после оптимизации:

app.jsФайл становится меньше и растет вместе с нимlazyComponent.js. Когда есть много компонентов ленивой загрузки, мы можем в определенной степени уменьшить размер файла загрузки первого экрана и улучшить скорость рендеринга первого экрана. В этом эксперименте в качестве примера используется только один компонент.Если количество ленивых загрузок велико, этого достаточно, чтобы значительно уменьшить размер app.js.

4. Проверьте эффективность оптимизации

1. Используйте Puppeteer и Performance API для сравнения

Чтобы проверить эффективность сделанной ранее оптимизации, я провел серию сравнительных экспериментов. Содержание эксперимента состоит в том, чтобы использовать puppeteer для посещения страниц до и после оптимизации 1000 раз соответственно, а также использовать Performance API для подсчета среднего значения пяти элементов данных в этих 1000 посещениях. Результаты эксперимента показаны на рисунке ниже, где:

  • A - среднее время запроса запроса

  • B — среднее время, затрачиваемое на синтаксический анализ дерева dom.

  • C — среднее время от завершения запроса до загрузки DOM.

  • D — среднее время от начала запроса до конца события domContentLoadedEvent.

  • E — среднее время от начала запроса до загрузки

     

Линейный график не может точно отобразить данные, поэтому данные в прикрепленной таблице выглядят следующим образом (все среднее времязатратно):

категория Оптимизировано До оптимизации

А (запрос запроса)

7.01 7.04

B (среднее значение дерева синтаксического анализа)

30.28 32.59

C (запрос выполняется до загрузки DOM)

552.86 582.0

D (от начала запроса до конца domContentLoadedEvent)

569.13 589.0

E (от начала до конца загрузки)

1055.59 1126.94

Из данных видно, что время запроса до и после оптимизации никак не влияет, но общее время загрузки значительно сокращается и сразу выходит на отметку в 1000 мс, видно, что скорость загрузки первого экрана все же значительно улучшилась. после оптимизации.

Примечание. В процессе запуска puppeteer 1000 раз будут возникать колебания сети, что приведет к большому объему запрашиваемых данных, поэтому среднее значение не может полностью отражать нормальное время запроса. Но в среднем 1000 раз достаточно, чтобы сравнить время запроса до и после оптимизации.

2. Используйте параметры производительности Chorme для сравнения

Поскольку параметры, предоставляемые Performance API, ограничены, я получил параметры для запроса одной страницы из сводки производительности браузера Chrome. Поскольку это единичные данные, мы не проводим подробного сравнения. Перечислено здесь только для того, чтобы показать, какие части времени рендеринга в браузере улучшились до и после оптимизации. До оптимизации:

Оптимизировано:

  • Синий: время загрузки уменьшено.

  • Желтый: время выполнения сценария уменьшено.

  • Фиолетовый: время рендеринга уменьшилось.

  • Зеленый: время рисования плоское

  • Серый: Другое (Другое) время сокращено

  • Idle: сокращено время простоя браузера.

Кроме того, я нашел из сети, что после оптимизации, поскольку парсинг страницы происходит относительно быстрее, чем раньше, время запроса основного интерфейса соответственно меньше.

V. Резюме

По ряду данных видно, чтоReact.lazyа такжеReact.SuspenseЕго использование в определенной степени ускоряет отрисовку первого экрана, ускоряя загрузку наших страниц. Кроме того, когда мы хотим добавить новую функцию и ввести новую зависимость, мы часто оцениваем размер зависимости и то, какое влияние введение зависимости окажет на исходный пакет. Если эта функция редко используется, то мы можем использовать ее с удовольствиемReact.lazyа такжеReact.Suspenseчтобы загружать функцию по запросу, не жертвуя удобством для пользователя.

6. Расширенное чтение

[1] https://webpack.js.org/guides/code-splitting/ [2] https://github.com/jamiebuilds/react-loadable [3] https://github.com/smooth-code/loadable-components