От автора перевода: Обращения и рекомендации во всей статье кроме 5 пункта идут от лица автора оригинальной статьи     Всем привет! В этой статье мы поговорим о мемоизации (запоминании), методе оптимизации, который может помочь сделать тяжелые вычислительные процессы более эффективными.     Мы начнем с разговора о том, что такое мемоизация (запоминание) и когда его лучше всего реализовать. Позже мы приведем практические примеры для JavaScript и React. Что такое мемоизация Как она работает Пример мемоизации в JavaScript Пример мемоизации в React 4.1 Pure components 4.2 PureComponent class 4.3 Компонент высшего порядка memo 4.4 Когда использовать хук useCallback4.5 Когда использовать хук useMemo 4.6 Когда использовать мемоизацию PS от автора перевода     В программировании мемоизация - это метод оптимизации, который делает приложения более эффективными и, следовательно, более быстрыми. Он делает это, сохраняя результаты вычислений в кэше и извлекая ту же информацию из кэша в следующий раз, когда она потребуется, вместо того, чтобы вычислять ее снова.     Проще говоря, метод состоит в том, чтобы сохранить в кэше выходные данные функции и заставить функцию проверять, находится ли каждое требуемое вычисление в кэше, прежде чем вычислять его.     Кэш - это просто временное хранилище данных, в котором хранятся данные, чтобы будущие запросы на эти данные могли обслуживаться быстрее.     Мемоизация - это простой, но мощный трюк, который может помочь ускорить наш код, особенно при работе с повторяющимися и тяжелыми вычислительными функциями.     Концепция мемоизации в JavaScript основана на двух концепциях: Замыкания: Комбинация функции и лексической среды, в которой эта функция была объявлена. Вы можете прочитать больше о них здесь и тут. Функции высшего порядка: Функции, которые работают с другими функциями, либо принимая их в качестве аргументов, либо возвращая их. Подробнее о них вы можете прочитать здесь.     Чтобы прояснить эту тумбу-юмбу, мы воспользуемся классическим примером последовательности Фибоначчи.     Последовательность Фибоначчи - это набор чисел, который начинается с единицы или нуля, за которым следует единица, и продолжается на основе правила, согласно которому каждое число (называемое числом Фибоначчи) равно сумме двух предыдущих чисел.     Это выглядит примерно так:     Допустим, нам нужно написать функцию, которая возвращает n-й элемент в последовательности Фибоначчи. Зная, что каждый элемент является суммой двух предыдущих, рекурсивное решение может быть следующим:     Если вы не знакомы с рекурсией, это просто концепция функции, которая вызывает саму себя, с каким-то базовым кейсом, чтобы избежать бесконечного цикла (в нашем случае, если (n <= 1)).     Если мы вызовем нашу функцию, подобную fib(5), под капотом наша функция будет выполняться следующим образом:     Видите, что мы выполняем fib(0), fib(1), fib(2) и fib(3) несколько раз. Что ж, это именно та проблема, которую помогает решить кэширование результатов вычислений.     При кэшировании нет необходимости пересчитывать одни и те же значения несколько раз – мы просто сохраняем каждое вычисление и возвращаем то же значение, когда оно потребуется вновь.     При использовании мемоизации наша функция будет выглядеть примерно следующим образом:     Что мы делаем в первую очередь, так это проверяем, получили ли мы объект memo в качестве параметра. Если мы этого не сделали, мы установили его как пустой объект:     Затем мы проверяем, содержат ли ключи переменной memo значение, которое мы получаем в качестве параметра. Если это так, мы просто вернем его. Вот где происходит волшебство. Нет необходимости в дополнительной рекурсии, как только мы сохраним наше значение в memo. =)     Если у нас еще нет значения в memo, мы снова вызываем fib, но теперь передаем memo в качестве параметра, поэтому вызываемые нами функции будут использовать те же запоминаемые значения, что и в "исходной" функции. Обратите внимание, что мы добавляем конечный результат в кэш перед его возвратом.     И это все! С помощью двух строк кода мы внедрили запоминание и значительно улучшили производительность нашей функции!     В React мы можем оптимизировать наше приложение, избегая ненужного повторного рендеринга компонентов с помощью запоминания. Как я уже упоминал в статье об управлении состоянием в React, компоненты повторно рендерятся из-за двух вещей: Изменения состояния Изменение props     Это именно та информация, которую мы можем "кэшировать", чтобы избежать ненужных повторных рендеров.     Но прежде чем мы перейдем к коду, давайте разберем некоторые важные понятия.     React поддерживает либо классовые, либо функциональные компоненты.  Functional component - это обычная функция JavaScript, которая возвращает JSX Class component - это класс JavaScript, который расширяет React.Component и возвращает JSX внутри метода рендеринга.     И что же тогда представляет собой pure component? Что ж, основываясь на концепции чистоты в парадигмах функционального программирования, функция считается чистой, если: Её возвращаемое значение определяется только его входными значениями Её возвращаемое значение всегда одинаковое для одинаковых входных значений     Точно так же React Component считается чистым, если он отображает один и тот же вывод для одного и того же состояния и props.     Функциональный чистый компонент может выглядеть следующим образом:     Мы передаем два props, а компонент отображает их. И рендеринг будет всегда одинаковым для одинаковых props.     Рассмотрим другой пример, мы добавляем случайное число к каждому prop перед рендерингом. Тогда результат может быть другим, даже если prop останутся прежними, так что это не будет считать чистым компонентом.     Те же примеры с использованием React.Component:     Для создания класса PureComponents для реализации меморизации React предоставляет базовый класс PureComponent.     Компоненты класса, расширяющие класс React.PureComponent имеет некоторые улучшения производительности и оптимизации рендеринга. Это связано с тем, что React реализует для них метод shouldComponentUpdate() с неглубоким сравнением props и состояний.     Давайте рассмотрим это на примере. Здесь у нас есть компонент класса, который представляет собой счетчик, с кнопками для изменения этого счетчика, добавляющего или вычитающего числа. У нас также есть дочерний компонент, которому мы передаем "name", представляющий собой строку.     Дочерний компонент - это чистый компонент, который просто отображает полученный prop.     Обратите внимание, что мы добавили логирование в обоих компонентах, так что мы получаем консольные сообщения каждый раз, когда они отображаются. И, говоря об этом, угадайте, что происходит, когда мы нажимаем кнопки увеличения или уменьшения? Наша консоль будет выглядеть следующим образом:     Дочерний компонент выполняет повторный рендеринг, даже если он получает один и тот же prop.     Чтобы внедрить мемоизацию и оптимизировать эту ситуацию, нам необходимо расширить React.PureComponent компонента в нашем дочернем компоненте, например:     После этого, если мы нажмем кнопку увеличения или уменьшения, наша консоль будет выглядеть следующим образом:     Просто начальный рендеринг дочернего компонента и никаких ненужных повторных рендеров, когда prop не изменился. Проще простого. ;)     При этом мы рассмотрели компоненты класса, но в функциональных компонентах мы не можем расширить класс React.PureComponent. Вместо этого React предлагает один HOC (High-Order Component) и два хука для работы с мемоизацией.     Если мы преобразуем наш предыдущий пример в функциональные компоненты, мы получим следующее:     Это вызвало бы ту же проблему, что и раньше, если бы дочерний компонент повторно отображался без необходимости. Чтобы решить эту проблему, мы можем обернуть наш дочерний компонент в компонент memo более высокого порядка, например, следующим образом:     Компонент более высокого порядка или HOC аналогичен функции более высокого порядка в javascript. Функции более высокого порядка - это функции, которые принимают другие функции в качестве аргументов ИЛИ возвращают другие функции. В React HOC берут компонент как prop и манипулируют им с какой-то целью, фактически не изменяя сам компонент. Вы можете представить это как компоненты-оболочки.     В этом случае memo выполняет ту же работу, что и Pure Component, избегая ненужного повторного отображения компонентов, которые он оборачивает.     Важно отметить, что memo не работает, если prop, передаваемый компоненту, является функцией. Давайте реорганизуем наш пример, чтобы увидеть это:     Теперь наш prop - это функция, которая всегда регистрирует одну и ту же строку, и ваша консоль снова будет выглядеть следующим образом:     Это связано с тем, что на самом деле при повторном рендеринге каждого родительского компонента создается новая функция. Итак, если создается новая функция, это означает, что у нас есть новый prop, и это означает, что наш дочерний компонент также должен быть повторно отрисован.     Чтобы справиться с этой проблемой, react предоставляет useCallback хук. Мы можем реализовать его следующим образом:     И это решает проблему ненужного повторного рендеринга дочерних элементов.     Что делает useCallback, так это удерживает значение функции, несмотря на повторный рендеринг родительского компонента, поэтому дочерний prop останется прежним до тех пор, пока значение функции также остается прежним.     Чтобы использовать его, нам просто нужно useCallback хук вокруг объявляемой нами функции. В массиве, присутствующем в хуке, мы можем объявить переменные, которые будут инициировать изменение значения функции, когда переменная тоже изменится (точно так же, как работает useEffect).     useMemo - это хук, очень похожий на useCallback, но вместо кэширования функции useMemo будет кэшировать возвращаемое значение функции.     В этом примере useMemo будет кэшировать число 2:     В то время как useCallback закеширует () => num + 1     Вы можете использовать useMemo очень похожим образом на memo HOC. Разница в том, что useMemo - это хук с массивом зависимостей, а memo - это HOC, который принимает в качестве параметра необязательную функцию, использующую props для обновления компонента с условием.     Более того, useMemo кэширует значение, возвращаемое между рендерами, в то время как memo кэширует весь react компонент между рендерами.    Мемоизация в React - хороший инструмент, который нужно иметь в своем арсенале, но это не то, что вы должны использовать везде. Эти инструменты полезны для работы с функциями или задачами, требующими больших вычислений.    Мы должны отдавать себе отчет в том, что в фоновом режиме все три этих решения также увеличивают накладные расходы на наш код. Поэтому, если повторный рендеринг вызван задачами, которые не требуют больших вычислительных затрат, возможно, лучше решить его другим способом или оставить в покое.     Я рекомендую эту статью Кента К. Доддса для получения дополнительной информации по этой теме.     Благодарю Вас если вы дочитали до этого момента. Меня зовут Руслан и я работаю Typescript Backend разработчиком. Это мой первый перевод для Хабра, поэтому буду благодарен любым комментариям и замечаниям.