Почти 60% посетителей сайта покидают его в том случае, если его загрузка занимает более 3 секунд. 80% таких посетителей на сайт уже не возвращается. Это говорит о том, что успех веб-проекта не в последнюю очередь зависит от его скорости. Автор материала, перевод которого мы сегодня публикуем, хочет рассказать о методиках повышения производительности React-приложений.


Результаты оптимизации приложения

Бенчмарки


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

  • В ходе проведения измерений, выполненных средствами вкладки Network инструментов разработчика Chrome, скорость передачи данных была принудительно ограничена до уровня быстрого 3G-соединения.
  • Показатель First Load получен при отключённом кэше.
  • Показатель 2nd Load указывает на время повторной загрузки приложения при включённом кэше.

Как видите, разница между оптимизированным и неоптимизированным приложением достаточно велика. Особенно это заметно в медленных сетях.

Размер бандла приложения был исследован с помощью source-map-explorer. Этот инструмент также позволяет узнать о том, сколько места занимают различные библиотеки. Показатели, которые можно видеть в верхней части рисунка, измерены с помощью Google Lighthouse.

Теперь расскажу о том, как я оптимизировал приложение.

1. Использование CSS вместо CSS-in-JS


В старой версии приложения я использовал библиотеку styled-components. Чем это плохо? Дело в том, что обычный CSS быстрее и занимает меньше места. Современные браузеры умеют загружать CSS-код параллельно с JavaScript-бандлом. Кроме того, для использования обычного CSS не нужно дополнительной библиотеки. Минифицированный вариант styled-components занимает порядка 54 Кб. Использование обычного CSS вместо styled-components привело к тому, что код приложения быстрее загружается, и к тому, что при изменении стилей системе приходится выполнять меньше вычислений.


Просто отказавшись от библиотеки styled-components и перейдя на обычный CSS можно сократить время загрузки сайта примерно на 0.3 секунды

Если CSS позволяет обеспечить лучшую производительность, чем технологии, реализующие схему CSS-in-JS, можно задаться вопросом о том, почему разработчики пользуются этой технологией стилизации компонентов. Среди причин выбора CSS-in-JS можно назвать то, что эта технология позволяет ограничивать область видимости стилей и отказаться от глобальной стилизации. Её удобно применять для работы с темами приложений. А кому-то, может быть, просто нравится стилизовать React-приложения именно так.

?Стили с ограниченной областью видимости


Create-react-app теперь официально поддерживает CSS-модули с ограниченной областью видимости. Это означает, что ограничивать область применения стилей можно и без применения дополнительных библиотек.

?Темы


Если вы работаете с библиотекой styled-components, то для того чтобы пользоваться переменными, определяющими темы, достаточно обернуть подобные переменные в ThemeProvider. Всё это хорошо, но по состоянию на май 2019 года 91% браузеров поддерживает похожую стандартную возможность CSS.

Если вы полагаете, что 91% — это недостаточно хороший показатель — поразмышляйте о том, что это, возможно, не так уж и мало.


Поддержка CSS-переменных

Собственно говоря, если вас не интересует поддержка IE — тогда вы смело можете использовать в своих проектах CSS-переменные. Если вам интересна эта тема — рекомендую взглянуть на данный материал.

2. Уход от больших CSS-библиотек



Анализ пакета material-ui

Я — большой любитель Material Design. Для React написана замечательная Material-библиотека, которая называется material-ui. У этой библиотеки есть лишь одна проблема. Это — её размер. Она очень велика. Даже если пользоваться лишь отдельными её компонентами, в бандл попадёт её реализация механизма CSS-in-JS, а это — примерно 30 Кб минифицированного кода.

Каковы альтернативы? Я решил построить собственные компоненты, стилизуя их в процессе создания приложения. Одной из причин такого выбора было то, что мне захотелось освежить знания в области CSS. А CSS-код я не писал уже давно. Однако здесь есть и другие возможности. В частности, речь идёт о CSS-фреймворках, размеры которых гораздо меньше, чем размер material-ui. Например — это Spectre и Bulma, код которых занимает, соответственно, 9 и 40 Кб после GZIP-сжатия.


Spectre — 9 Кб после GZIP-сжатия


Bulma — 40 Кб после GZIP-сжатия

3. Ленивая загрузка страниц


Итак, у вас есть роутер со множеством импортированных страниц. Если речь идёт о паре страниц — никаких проблем тут нет. Но по мере увеличения количества страниц растёт и время первого вывода сайта на экран. Вот как могут выглядеть команды импорта:

import NotFound from "pages/NotFound";
import Projects from "pages/Projects";
import Project from "pages/Project";

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

import React, { lazy, Suspense } from "react";

const load = (Component: any) => (props: any) => (
    <Suspense fallback={<Loader />}>
        <Component {...props} />
    </Suspense>
);

const NotFound = load(lazy(() => import("pages/NotFound")));
const Projects = load(lazy(() => import("pages/Projects")));
const Project = load(lazy(() => import("pages/Project")));

4. Технологии прогрессивных веб-приложений


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

5. Избавление от пакетов, которые кажутся интересными, но большой пользы не приносят


В моём исходном проекте при монтировании компонентов использовалось много анимации, которая оживляла загрузку страницы. Всё это не просто замедляло страницу. Это делало её гораздо медленнее, чем она могла бы быть. Я, глядя на сайт, и радуясь тому, как симпатично он выглядит, до определённого момента не задумывался о производительности. Но на сайте была не только анимация. Там были и другие подобные украшательства. Например — кнопка, которая позволяла перейти в верхнюю часть страницы. Например — анимированная загрузка некоторых элементов. Всё это мне нравилось, но при ближайшем рассмотрении оказалось, что, например, сайт реально тормозит на не самых быстрых устройствах. Причём, сначала я тестировал сайт исключительно на ноутбуке, поэтому узнал об этом далеко не сразу.

Ещё у меня был компонент-слайдер, который я добавил на страницу без особых размышлений о его «весе». Его я использовал для показа слайд-шоу. Как оказалось позднее, один только код этого слайдера занимал, в минифицированном виде, 30 Кб. Тогда я решил самостоятельно создать компонент для показа слайд-шоу. Его минифицированный код, в итоге, занял 25 КБ. В этот объём входила хорошая библиотека анимации и система работы с жестами, которые можно использовать не только для слайд-шоу, но и в других частях приложения. И выглядит то, что у меня получилось, гораздо лучше, чем стороннее решение.

Вот слайдер из NPM.


Слайдер из NPM

Вот мой слайдер


Слайдер собственной разработки (в жизни он работает гораздо более плавно, чем на этом GIF-изображении с низкой частотой кадров)

Посмотреть этот слайдер в действии можно здесь.

?Анализ размера бандла


Если вы пользуетесь create-react-app, то вам очень просто проанализировать состав бандла. Для этого выполните команду npm run build, а после этого — команду npx source-map-explorer "build/static/js/*.js". После этого откроется страница со сведениями о составе бандла, напоминающая ту, что показана ниже.


Сведения о бандле

Итоги


Как видите, ускорять React-приложения не так уж и сложно. Достаточно внимательно следить за тем, из чего они построены, тестировать их и вносить в них соответствующие изменения. Вот проект, о котором шла здесь речь, до улучшения, а вот — после.

Если вы интересуетесь оптимизацией React-приложений — вот несколько наших публикаций на эту тему:


Уважаемые читатели! Есть ли у вас примеры удачной (или неудачной) оптимизации React-приложений?



Комментарии (19)


  1. red_perez
    17.06.2019 12:47

    Почти 60% посетителей сайта покидают его в том случае, если его загрузка занимает более 3 секунд. 80% таких посетителей на сайт уже не возвращается.

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


    1. Duster
      18.06.2019 10:50

      Особенно бесит, когда анимация сделана ради анимации, и приходится либо ловить ссылку мушкой, либо ждать завершения «вау я у мамы дезигнер» выпендрежа.


    1. Peter1010
      19.06.2019 07:31

      Тут ещё смотря что имеется в виду под загрузкой.
      Если я зашёл и у меня белый экран, то да, секунда 2-3 и человек закроет. (но лично я проверю расширение NoScript).
      В вот, если появится хотя бы полоска загрузки, или ещё что-то указывающее что сайт загружается, и надо подождать, то ок.


  1. alexesDev
    17.06.2019 12:51

    styled-componets v3 были в 3 раза медленнее обычного css. Половина этих лагов связна с поддержкой тем, который мы даже не использовали. И используя styled-components стоит понимать что вместо обычного 'div' строки, которая уходит в недра react, получается class MyCustomDivWithStyles extends React.Component. Это тысячи новых классов на пустом месте, если использовать везде.


    Если в проекте уже используется styled-components, то можно убрать его на верхних уровнях дерева (всякие layouts и тп) и прилично ускорить приложение. Те styled-components плюс минус нормально работает для всяких Button, а для MainLayout — дикий ужас.


    1. Carduelis
      17.06.2019 14:14

      Только уже давно вышла v4, которая значительно быстрее.
      Уже почти вышла пятая версия: 50% faster server-side rendering, 20% faster client-side rendering, 19% smaller bundle size, RTL support and no breaking changes!


      Честно говоря, проблема с множествами однотипными компонентами решается их мемоизацией. Этот самый MainLayout значительно ускорился, когда я добавил React.memo().


      1. alexesDev
        17.06.2019 14:34

        Может быть у меня психологическая травма после переписывания styled-components на css. Но это увеличило время первого рендера (react.memo) тут не поможет с 800мс до 200мс.


        1. Carduelis
          17.06.2019 15:45

          Не важно, первый рендер тоже будет ускорен. Вы же не используете на странице каждый раз каждый элемент с новым набором props? Всякие тормозящие обертки из <Box> или <Grid> будут закешированы после первого же использования в корне лэйаута.


        1. Carduelis
          17.06.2019 15:47

          Кстати, когда вы получали свою травму (no offence), как вы переделывали динамические свойства? Каждый раз использовали state? Или написали свой переключатель (классов и/или инлайн-стилей)?


        1. Carduelis
          17.06.2019 15:51

          Кстати, я решил перевести статью о пятой версии: https://habr.com/ru/post/456422/


    1. irsick
      17.06.2019 22:12

      получается class MyCustomDivWithStyles extends React.Component

      Используйте функциональные компоненты и makeStyles


  1. acsent1
    17.06.2019 18:12

    Каким инструментом можно проверить свой сайт?


    1. KhodeN
      17.06.2019 22:35

      Начните со встроенной в Chrome вкладки Audits (там Google Lighthouse)


  1. comerc
    17.06.2019 23:56

    Я — большой любитель Material Design. Для React написана замечательная Material-библиотека, которая называется material-ui. У этой библиотеки есть лишь одна проблема. Это — её размер. Она очень велика. Даже если пользоваться лишь отдельными её компонентами, в бандл попадёт её реализация механизма CSS-in-JS, а это — примерно 30 Кб минифицированного кода. Каковы альтернативы? Я решил построить собственные компоненты, стилизуя их в процессе создания приложения.

    Обнять и плакать. babel-plugin-import


    1. faiwer
      19.06.2019 20:17

      Так он же выше и пишет, что "пользоваться лишь отдельными её компонентами". Что мол сборка-минимум уже 30 KiB


  1. mrigi
    18.06.2019 01:03

    Хотел бы добавить, что проблем добавляют сторонние скрипты типа яндекс метрик и вконтактов, залинкованные синхронно. К примеру в Украине эти ресурсы заблокированы и сайты с такими линками не грузятся практически вообще. И сколько не ускоряй React, пользователь уйдет.


  1. alsii
    18.06.2019 09:04
    +1

    Я отдаю себе отчет в том, что это перевод, но все же мне кажется, что в первой фразе поста:


    Почти 60% посетителей сайта покидают его в том случае, если его загрузка занимает более 3 секунд. 80% таких посетителей на сайт уже не возвращается.

    утеряно начало с упоминанием британских ученых.


  1. JiLiZART
    18.06.2019 11:58

    Еще можно вот такое чудо сделать и выйграть дофига килобайтов


    .babelrc


    presets: [
      ['@babel/react', {pragma: 'h'}]
    ]

    webpack.config.js


    plugins: [
      new webpack.ProvidePlugin({
          h: ['react', 'createElement']
      })
    ]


    1. JiLiZART
      18.06.2019 12:01

      А еще можно вместо React подключить https://preactjs.com/ и еще выйграть кучу килобайтиков


      1. faiwer
        19.06.2019 20:20

        а маленьким шрифтом со звёздочкой не стоило добавить: "на ранних этапах создания приложения"? :)


        Хак с Pragma-h после gZip уже не будет таким впечатляющим. А вот вырезание prop-types должно дать ощутимый прирост.