Привет! Меня зовут Чаглаян Яникоглу, и я работаю фронтенд-разработчиком в Jotform. Я также являюсь инструктором/консультантом по интерфейсу в Patika.dev и kodluyoruz.

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

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

Во-первых, давайте определим веб-производительность и посмотрим, почему это важно.

Веб-производительность относится к

  • Как быстро контент сайта загружается и отображается в веб-браузере
  • Насколько хорошо сайт реагирует на взаимодействие с пользователем

Хорошая цель для веб-производительности состоит в том, чтобы пользователи даже не знали, как работает сайт.

Веб-производительность важна, потому что она

  • Создает положительный пользовательский опыт
  • Улучшает коэффициент конверсии: согласно исследованию Akamai, секундная задержка во время загрузки может привести к снижению конверсии на 7 процентов.

Скорость сайта напрямую влияет на показатели отказов, конверсию, доход, удовлетворенность пользователей и рейтинг в поисковых системах.

Как работает реакция

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

У нас есть два важных ключа в React: виртуальная DOM (объектная модель документа) и разница.

Виртуальный DOM на самом деле является копией реального DOM. Когда мы создаем визуализированный компонент, React создает виртуальную DOM для своего дерева элементов в компоненте.

Затем всякий раз, когда состояние компонента изменяется, React воссоздает виртуальное дерево DOM и сравнивает результат с предыдущим рендерингом. Затем он только обновляет измененный элемент в фактическом DOM. Этот процесс называется различением.

React использует концепцию виртуального DOM, чтобы свести к минимуму затраты производительности на повторный рендеринг веб-страницы, поскольку манипулирование реальным DOM обходится дорого.

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

Здесь мы можем сделать вывод, что изменение состояния в компоненте React вызывает повторный рендеринг. Точно так же, когда состояние передается дочернему компоненту в качестве реквизита, он перерисовывает дочерний компонент и так далее.

Улучшение производительности React

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

Вот несколько способов оптимизировать производительность в React.

1. Сохраняйте состояния локальными, но зачем?

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

Однако, если незатронутый компонент выполняет дорогостоящие вычисления и мы замечаем проблемы с производительностью, нам следует оптимизировать наше приложение React.

В приведенном выше коде при изменении входного текста компонент приложения будет отображаться. Хотя состояние или реквизиты ChildComponent не изменились, он будет повторно отображаться.

Для такого небольшого компонента повторный рендеринг может быть не важен, но подумайте о более дорогих компонентах.

Итак, как мы это исправим?

В приведенном выше коде мы создали новый компонент — FormInput — и переместили состояние в локальный компонент. Теперь, если состояние FormInput изменится, это не повлияет на ChildComponent.

Таким образом, мы должны сохранять локальные состояния, если можем. Например; Модальные состояния, состояния ввода поиска и т. д.

2. Мемоизация

В этой технике мы обмениваем пространство памяти на время. Мемоизация — это стратегия оптимизации, которая кэширует операцию, визуализируемую компонентом. Сохраняет результат в памяти.

Существует три техники запоминания:

  • React.memo (функциональный компонент) / React.PureComponents (компонент класса)
  • хук useCallback
  • использовать крючок для заметок

Используя React.memo()

React.memo — это компонент более высокого порядка, используемый для обертывания чисто функционального компонента, чтобы предотвратить повторный рендеринг, если свойства, полученные в этом компоненте, никогда не меняются.

React.memo() работает довольно хорошо, когда мы передаем примитивные значения, такие как число в нашем примере. И, если вы знакомы с ссылочным равенством, примитивные значения всегда ссылочно равны и возвращают true, если значения никогда не меняются.

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

Использование крючка useCallback

С хуком useCallback функция incrementCount переопределяется только при изменении массива зависимостей count

const incrementCount = React.useCallback(() => setCount(count + 1), [count]);

В приведенном выше примере, если мы не обернем функцию setCount хуком useCallback, то при каждом изменении ввода ChildComponent будет снова отображаться независимо от того, изменяется ли количество или incrementCount.

Поскольку функция является типом объекта, то есть типом реферера, она будет создавать новый экземпляр при каждом рендеринге. Поэтому, когда мы используем useCallback, он будет кэшировать экземпляр функции при каждом рендеринге, поэтому ChildComponent не будет повторно рендериться.

Использование крючка useMemo

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

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

Подобно useCallback, хук useMemo также ожидает функцию и массив зависимостей.

useCallback против useMemo

Как видно на изображении выше, useCallback кэширует функцию в памяти, а useMemo кэширует результат.

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

Итак, когда мы должны использовать React.memo, useCallback или useMemo?

Их можно использовать, когда

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

3. Реагировать на фрагменты

Фрагменты позволяют вам группировать список дочерних компонентов вместе. Использование фрагментов

  • Уменьшает количество лишних тегов
  • Предотвращает рендеринг дополнительных элементов DOM

Каждый компонент должен иметь один родительский тег.

return (
  <>
    <h1>This is header</h1>
    <p>Hello Medium</p>
  </>
)

4. Разделение кода/динамический импорт

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

С разделением кода React позволяет нам разделить большой файл пакета на несколько фрагментов с помощью динамического импорта () с последующей ленивой загрузкой этих фрагментов по требованию с помощью React.lazy. Эта стратегия значительно повышает производительность страницы сложного приложения React.

import React, { useState, Suspense } from "react";
const Home = React.lazy(() => import("./components/Home"));
const About = React.lazy(() => import("./components/About"));
function App() {
  const [page, setPage] = useState("home");
  return (
    <div>
      {page === "home" && (
        <Suspense fallback={<div>Loading...</div>}>
          <Home />
        </Suspense>
      )}
      {page === "about" && (
        <Suspense fallback={<div>Loading...</div>}>
          <About />
        </Suspense>
      )}
      <div>
        <button onClick={() => setPage("home")}>Home</button>
        <button onClick={() => setPage("about")}>About</button>
      </div>
    </div>
  );
}
export default App;

5. Список виртуализации / работы с окнами

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

Вы можете использовать пакет npm для управления окнами. Самые популярные пакеты npm — это react-window и react-virtualized.

6. Ленивая загрузка изображений

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

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

Два популярных пакета npm для этого — react-lazyload и react-lazy-load-image-component.

7. Дросселирование и устранение дребезга событий

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

Примером устранения дребезга является поиск с вызовом AJAX. Например, предположим, что вы хотите добавить поиск в реальном времени из API на свой веб-сайт.

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

Поэтому лучший способ справиться с этим — отложить выполнение запроса.

Я также опубликовал пример дроссельной заслонки. Вы должны проверить это :)

Используйте Infinitive Scroll как профессионал

8. Веб-воркеры

JavaScript — это однопоточное приложение для обработки синхронных исполнений.

Пока веб-страница отображается, она выполняет несколько задач, в том числе

  • Обработка взаимодействий с пользовательским интерфейсом
  • Обработка данных ответа
  • Управление элементами DOM
  • Включение анимации

Все эти задачи решаются в одном потоке.

Рабочие веб-процессы — это способ уменьшить нагрузку выполнения на основной поток.
Рабочие потоки — это фоновые потоки, которые позволяют пользователю выполнять несколько скриптов и выполнение JavaScript, не прерывая основной поток.

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

Как вы измеряете производительность?

Итак, мы только что говорили о том, как повысить производительность. Но как мы можем измерить производительность React, чтобы убедиться, что эти улучшения работают? Вот несколько инструментов, чтобы сделать именно это.

1-) PageSpeed ​​Insights
PageSpeed ​​Insights — это инструмент, созданный Google, позволяющий людям определять лучшие методы повышения производительности на любом веб-сайте и получать предложения по оптимизации, которые можно использовать для ускорения этого веб-сайта.

Как видно на изображении выше, мы можем проверить производительность в Интернете с помощью расширения Lighthouse для Google Chrome. Это даст нам показатели как для настольных компьютеров, так и для мобильных устройств.

2-) Profiler
Согласно React, Profiler измеряет, как часто приложение React выполняет рендеринг и какова «стоимость рендеринга. Его цель — помочь определить части приложения, которые работают медленно и могут выиграть от оптимизаций, таких как запоминание.

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

При принятии решения об использовании метода оптимизации, такого как запоминание и, в частности, useCallback():

  1. Используйте Profiler для измерения скорости и стоимости рендеринга приложения.
  2. Количественно определите повышение производительности, к которому вы стремитесь (например, увеличение скорости рендеринга на 150 миллисекунд против 50 миллисекунд)
  3. Спросите себя: стоит ли использовать useCallback() повышенную производительность по сравнению с повышенной сложностью?

Заключение

Чтобы успешно оптимизировать наше приложение React, мы должны сначала найти проблему с производительностью в нашем приложении, которую нужно исправить.

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

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

Удачных взломов! :)