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

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

Думая о разработке React на основе компонентов, думайте о «результатах вычислений» как о функциональных компонентах. Мемоизация возможна и с компонентами класса, но в этой статье будут рассмотрены только функциональные компоненты.

Пример использования

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

Проблема

Ниже у нас есть функциональный компонент Table с двумя отдельными состояниями, count и buttonData. Кнопка, которая изменяет состояние count, существует в родительском компоненте, и это состояние не передается ни одному из его дочерних элементов. По этой причине мы не хотим, чтобы Button перерендеривался при изменении count, потому что им нечего делать вместе.

Вот пользовательский компонент Button, который отображает компонент Table.

Важно: обратите внимание, что свойства, передаваемые Button, деструктурированы. Когда memo сравнивает предыдущий и новый реквизит, он использует только поверхностное сравнение, поэтому объект props всегда будет вызывать повторный рендеринг. Это связано с тем, что для сравнения сложной структуры данных требуются дополнительные накладные расходы.³
В этом коде каждый раз, когда состояние count обновляется в родительском компоненте Table, дочерний компонент Button будет повторно отображаться. Это пустая трата вычислений, и ее можно предотвратить для повышения производительности.

Решение

Чтобы предотвратить эти дорогостоящие и ненужные повторные рендеры, все, что нам нужно сделать, это использовать memo в компоненте Button.

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

Теперь всякий раз, когда вы меняете состояние count в родительском компоненте Table (или любом другом состоянии, не связанном с buttonData, если уж на то пошло), дочерний компонент Button сравнивает свои предыдущие и текущие реквизиты и решает не выполнять повторный рендеринг.

Когда вы хотите сделать повторный рендеринг

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

Обратите внимание на строку 22. Здесь мы создаем копию состояния, меняем одно свойство одного из объектов в массиве состояний и устанавливаем состояние с тем же массивом. Хук useState воспринимает это как обновление состояния с его старым состоянием и, следовательно, не запускает рендеринг. Очень простое решение этого ниже.

Решение здесь заключается в использовании оператора распространения, «расширяющего» массив в новый массив. Это запустит рендеринг родительского компонента, передав новые реквизиты (идентификатор и метку) дочернему компоненту Button. memo сравнит старые пропсы с новыми, и, поскольку сейчас пропсы фактически изменились, компонент снова отрендерится.

Еще одна вещь, на которую следует обратить внимание, заключается в том, что будет отображаться только компонент Button с измененными значениями реквизита, а не весь список Button.

Иметь ввиду

При запоминании функционального компонента сравниваются только реквизиты, которые отправляются компоненту для определения рендеринга. Если сам компонент Button имеет состояние или контекст, используя хуки useState, useReducer или useContext, тогда он будет отображаться так, как ожидалось.

В части 2 я расскажу о useCallback, useMemo и useRef.

GitHub:

https://github.com/AidanMcB/Memoization-in-React/tree/memoization-part-1

Ресурсы:

  1. https://reactjs.org/docs/react-api.html#reactmemo
  2. https://www.freecodecamp.org/news/memoization-in-javascript-and-react/#:~:text=In%20programming%2C%20memoization%20is%20an,вместо%20of%20computing%20it%20again .
  3. https://blog.openreplay.com/improving-react-application-performance-react-memo-vs-usememo/#:~:text=memo%20использует%20shallow%20comparison%20by,%20хуже%20%20накладные расходы %20is.