Существует множество методов оптимизации, которые вы можете использовать для повышения общей производительности ваших приложений React. Один из таких приемов — мемоизация. В этом руководстве вы узнаете, что такое мемоизация и как использовать мемоизация в React для оптимизации ваших приложений React.
Содержание статьи
Простая мемоизация
Мемоизация — один из методов оптимизации, используемых в программировании. Это позволяет сэкономить время и ресурсы, избегая ненужных вычислений. Вычисления не требуются, если результат вычисления такой же, как результат предыдущей компиляции.
Рассмотрим простой пример. Представьте, что у вас есть функция, которая возвращает факториал заданного числа. Обычно эта функция выполняет вычисление для каждого числа, которое вы ей даете. Это необходимо? Например, предположим, что вы запускаете функцию два или три раза с одним и тем же номером.
Необходимо ли в этом случае выполнить все вычисления, чтобы вернуть значение, которое эта функция уже видела в прошлом? Нет. Чтобы предотвратить это, вы можете создать кеш и изменить функцию. Каждый раз, когда функция запускается, она сначала заглядывает в кэш.
Если номер, который вы указали функции, уже находится в кеше, ничего вычислять не нужно. Эта факториальная функция может просто вернуть известный результат для этого числа. Если числа нет в кеше, функция факториала может выполнить свою работу и вычислить факториал и добавить его в кеш.
// Создать кеш:
let cache = [1]
// Создаем мемоизированную факториальную функцию:
function getFactorialMemoized (key) {
if (! cache [key]) {
// Добавляем новое значение в кеш:
кеш [key] = ключ * getFactorialMemoized (ключ - 1)
} еще {
// Возвращаем кешированное значение:
console.log ('попадание в кеш:', ключ)
}
// Возвращаем результат
кэш возврата [key]
}
getFactorialMemoized (6)
getFactorialMemoized (6)
Этот пример демонстрирует суть мемоизации. Вы вычисляете некоторые значения и сохраняете их, запоминаете для дальнейшего использования. Если через какое-то время в будущем вам понадобится получить одно из этих значений, вам не придется вычислять их снова. Вместо этого вы извлекаете их из своего хранилища, некоторого кеша.
Как вы, наверное, догадались, этот метод может значительно улучшить производительность. Обычно гораздо быстрее и экономичнее просто вернуть какое-то значение вместо его вычисления. Звучит здорово, но как использовать мемоизацию в React?
Мемоизация в React
Хорошая новость заключается в том, что React предоставляет встроенные инструменты для мемоизации "из коробки". Это означает, что вам не нужно добавлять никаких дополнительных зависимостей. Единственная нужная вам зависимость — это реакция и реакция. На данный момент React предоставляет три инструмента мемоизации: memo ()
useMemo ()
и useCallback ()
.
.
Памятка
Первым инструментом для мемоизации в React является компонент более высокого порядка (HOC), называемый memo ()
. Компонент высокого порядка берет один компонент React и возвращает новый. В memo ()
есть одно важное отличие. Этот новый возвращенный компонент также запомнен.
Это означает, что React не будет повторно отображать этот мемоизированный компонент, если его не нужно обновить. Это означает, что до тех пор, пока свойства компонента остаются неизменными, React будет пропускать повторный рендеринг мемоизированного компонента. Вместо этого он будет повторно использовать результат последнего рендеринга.
Когда React обнаруживает, что некоторые свойства компонента изменились, он повторно визуализирует компонент. Это необходимо для обеспечения актуальности и синхронизации пользовательского интерфейса. Когда дело доходит до памятки ()
следует упомянуть два важных момента.
// Ввозная записка
импортировать {memo} из "реагировать"
// Компонент без памятки:
export const App = () => {
возвращение (
Это обычный компонент
)
}
// Компонент, обернутый памяткой:
export const App = memo (() => {
возвращение (
Это мемоизированный компонент
)
})
Местные государства
Во-первых, React будет следить только за изменениями свойств. Он не следит за изменениями в логике внутри компонента. Это также не помешает этим изменениям повторно отрисовать компонент. Одним из примеров такого изменения является то, что этот компонент имеет собственное локальное состояние.
При изменении локального состояния компонент все равно будет повторно визуализирован. Это сделано специально, чтобы обеспечить синхронизацию пользовательского интерфейса и даты. Это также относится к компонентам, подключенным к поставщикам или магазинам redux. Изменение этих объектов данных приведет к повторной визуализации связанных с ними компонентов.
Рассмотрим простой пример. Представьте, что у вас есть компонент, отслеживающий количество отсчетов. Он отображает текущий счетчик и кнопку для увеличения счетчика на 1. Даже если сам компонент запомнен, каждый щелчок по кнопке приведет к повторному рендерингу.
Важно помнить, что это не ошибка, а функция. React повторно визуализирует компонент, чтобы отображаемое значение счетчика синхронизировалось с данными в локальном состоянии компонента. Без повторного рендеринга отрендеренное число останется на 0.
// Импортировать памятку и useState:
импортировать {memo, useState} из "реагировать"
export const App = memo (() => {
// Создаем локальное состояние:
const [count, setCount] = useState (0)
// Это будет регистрироваться при каждом повторном рендеринге:
console.log ('Визуализация')
// Создаем обработчик кнопки:
const onCountClick = () => setCount ((prevCount) => ++ prevCount)
возвращение (
Текущее количество: {count}
)
})
Поверхностное сравнение
Во-вторых, React выполняет лишь поверхностное сравнение свойств запоминаемых компонентов. Этого может быть недостаточно, если вы передаете через props более сложные данные, чем примитивные типы данных. В этом случае memo ()
HOC также позволяет передавать вашу собственную функцию сравнения в качестве второго аргумента.
Эта настраиваемая функция сравнения имеет два параметра: предыдущий и следующий свойства. Внутри этой функции вы можете выполнить любую нужную вам логику сравнения.
// Импортировать меморандум и лодаш:
импортировать {memo} из "реагировать"
импортировать {isEqual} из lodash
// Создаем пользовательскую функцию сравнения:
function isEqual (prevProps, nextProps) {
// Возвращаем результат произвольного сравнения:
return isEqual (prevProps, nextProps)
}
// Компонент, обернутый памяткой:
export const App = memo (() => {
возвращение (
Это мемоизированный компонент
)
}, isEqual) // Передаем пользовательскую функцию сравнения
useMemo
Второй инструмент, который помогает с мемоизацией в React, — это React hook useMemo (). В отличие от memo ()
ловушка useMemo
позволяет выполнять некоторые вычисления и запоминать их результат. Затем, пока отслеживаемый ввод остается неизменным, useMemo ()
будет возвращать кешированный результат, избегая ненужных вычислений.
Простой пример
Например, представьте, что некоторые компоненты получают номер через реквизиты. Затем он берет это число и вычисляет его факториал. Это сложное вычисление, которое мы хотим оптимизировать с помощью мемоизации. Компонент также имеет локальное состояние. Это счетчик, с которым мы уже играли.
Мы добавим функцию для вычисления факториала и будем использовать эту функцию для вычисления факториала и присвоения результата обычной переменной. Что случится? Факториал будет вычислен при монтировании компонента. Проблема в том, что он также будет вычислен, когда мы нажмем кнопку подсчета и увеличим счетчик.
// Импортируем useState и useMemo:
импортировать {useState, useMemo} из "реагировать"
export const App = ({число}) => {
// Создаем локальное состояние:
const [count, setCount] = useState (0)
// Создаем обработчик кнопки:
const onCountClick = () => setCount ((prevCount) => ++ prevCount)
// Создаем факториальную функцию:
const getFactorial = (число) => {
// Распечатать журнал при запуске функции:
console.log ('факториал подсчета')
// Возвращаем факториал:
return num === 1? число: число * getFactorial (число - 1)
}
// Вычислить факториал для числового реквизита:
const factorial = getFactorial (число)
// ЭТО ^ проблема.
// Эта переменная будет переназначена,
// и факториал пересчитывается при каждом повторном рендеринге,
// каждый раз, когда мы нажимаем кнопку, чтобы увеличить счетчик.
возвращение (
Счетчик: {count}
Факториал: {факториал}
)
}
В приведенном выше примере мы видим, что факториал пересчитывается, потому что каждый раз, когда мы нажимаем кнопку, журнал внутри getFactorial ()
печатается в консоли. Это означает, что каждый раз при нажатии кнопки выполняется функция getFactorial ()
даже если число в реквизитах одинаковое.
Простое решение
Эту проблему можно быстро решить с помощью хука useMemo ()
. Все, что нам нужно сделать, это заключить вызов функции getFactorial ()
в useMemo ()
. Это означает, что мы назначим переменную factorial
с помощью хука useMemo ()
и передадим в ловушку функцию getFactorial ()
.
Мы также должны убедиться, что факториал будет пересчитываться при изменении числа, переданного через свойства. Для этого мы указываем эту опору как зависимость, которую хотим наблюдать в массиве зависимостей хуков useMemo ()
.
// Импортируем useState и useMemo:
импортировать {useState, useMemo} из "реагировать"
export const App = ({число}) => {
// Создаем локальное состояние:
const [count, setCount] = useState (0)
// Создаем обработчик кнопки:
const onCountClick = () => setCount ((prevCount) => ++ prevCount)
// Создаем факториальную функцию:
const getFactorial = (число) => {
// Распечатать журнал при запуске функции:
console.log ('счетчик факториал')
// Возвращаем факториал:
return num === 1? число: число * getFactorial (число - 1)
}
// Вычислить и запомнить факториал для числового реквизита:
const factorial = useMemo (() => getFactorial (число), [number])
// 1. Оберните функцию getFactorial () с помощью useMemo
// 2. Добавьте "число" в массив зависимостей ("[number]"), чтобы сообщить React, что он должен следить за изменениями этого свойства.
возвращение (
Счетчик: {count}
Факториал: {factorial}
)
}
Благодаря этому простому изменению мы можем предотвратить ненужные вычисления, которые в противном случае могли бы замедлить работу нашего приложения React. Таким образом, мы можем запомнить любые необходимые вычисления. Мы также можем использовать useMemo ()
несколько раз, чтобы свести к минимуму вычисления при повторной визуализации.
// Импортируем useState и useMemo:
импортировать {useState, useMemo} из реакции
export const App = () => {
// Добавляем состояние для принудительного повторного рендеринга
const [count, setCount] = useState (0)
// Добавляем обработчик кнопки:
const onCountClick = () => setCount ((prevCount) => ++ prevCount)
// Добавляем фиктивные данные и запоминаем их:
const users = useMemo (
() => [
{
full_name: 'Drucy Dolbey',
gender: 'Male',
},
{
full_name: 'Ewart Sargint',
gender: 'Male',
},
{
full_name: 'Tabbi Klugel',
gender: 'Female',
},
{
full_name: 'Cliff Grunguer',
gender: 'Male',
},
{
full_name: 'Roland Ruit',
gender: 'Male',
},
{
full_name: 'Shayla Mammatt',
gender: 'Female',
},
{
full_name: 'Inesita Eborall',
gender: 'Female',
},
{
full_name: 'Kean Smorthit',
gender: 'Male',
},
{
full_name: 'Celestine Bickerstaff',
gender: 'Female',
},
],
[]
)
// Подсчитываем пользователей-женщин и запоминаем результат:
const femaleUsersCount = useMemo (
() =>
users.reduce ((acc, cur) => {
console.log ('Вызвать сокращение')
return acc + (cur.gender === 'Женский'? 1: 0)
}, 0),
[users]
)
возвращение (
Количество пользователей: {femaleUsersCount}
)
}
В приведенном выше примере недостаточно запоминания результата присваивания femaleUsersCount
. Мы также должны запомнить пользователей
. В противном случае переменная users
будет переназначаться каждый раз при повторной визуализации компонента. Это также вызовет useMemo ()
для femaleUsersCount
. Это означало бы, что на самом деле ничего не запоминается.
Когда мы запоминаем пользователей
мы предотвращаем его повторное назначение. Это предотвратит ненужное изменение пользователей
и, следовательно, femaleUsersCount
. В результате изменится только счетчик
. На самом деле onCountClick ()
также будет воссоздан заново. Это подводит нас к последнему инструменту мемоизации в React.
useCallback
Мы можем многое сделать с помощью memo ()
и useMemo ()
чтобы использовать мемоизацию в React, чтобы избежать ненужных вычислений различного рода. Есть еще одна проблема, которую мы еще не решили. Каждый раз, когда компонент повторно выполняет рендеринг, он также воссоздает все локальные функции. Это обоюдоострый меч.
Две проблемы с воссозданными функциями
Это палка о двух концах, потому что она может привести к двум проблемам. Во-первых, все функции, которые вы объявляете в компоненте, будут воссоздаваться при каждом рендеринге. Это может иметь или не иметь значительного влияния, в зависимости от того, сколько функций у вас обычно есть. Вторая проблема может вызвать больше проблем.
Простой пример. Допустим, у вас есть один родительский и один дочерний компоненты. Родительский компонент создает локальное состояние и функцию. Эта функция также передается потомку через props, поэтому ее можно использовать там. Проблема? Вы помните, что такое memo ()
и поверхностное сравнение?
Дело в том, что когда вы передаете функцию компоненту, вы передаете комплексное значение, а не примитивное. Поверхностное сравнение React здесь не сработает. Он сообщит вам, что значение отличается, и повторно отобразит компонент, даже если значение такое же. В нашем случае значение — это функция.
При повторном рендеринге родительского компонента также воссоздается функция, которую он передает дочернему компоненту. Когда передается повторно созданная функция, React не может распознать, что функция, даже если она была создана заново, на самом деле такая же, как и предыдущая.
В результате будет выполнен повторный рендеринг дочернего компонента. Это просто произойдет независимо от того, используете вы memo ()
или нет.
// Дочерний компонент:
импортировать {memo} из "реагировать"
экспорт const CountChild = memo ((props) => {
console.log ('Отрисовка CountBox')
возврат
})
// Родительский компонент:
импортировать {useState, memo, useCallback} из 'react'
// Импортируем дочерний компонент
импортировать {CountChild} из './countChild'
export const App = memo (() => {
// Добавляем состояние для принудительного повторного рендеринга
const [count, setCount] = useState (0)
// Добавляем обработчик кнопки:
const onCountClick = () => {
setCount ((prevCount) => ++ prevCount)
}
возвращение (
count: {count}
)
})
Предотвращение повторного рендеринга, вызванного функциями, переданными через реквизиты
Чтобы избежать этого, используйте ловушку useCallback (). Вместо того чтобы объявлять функцию, как обычно, мы можем передать ее в качестве обратного вызова ловушке useCallback ()
и присвоить ее переменной. Это и правильно настроенные зависимости массивов гарантируют, что функция будет воссоздана только при необходимости.
Это означает только при изменении одной из зависимостей. Когда происходит повторный рендеринг и если зависимости не меняются, React будет использовать кешированную версию функции вместо ее повторного создания. React, возвращающий кешированную версию функции, также предотвратит ненужную повторную визуализацию дочернего компонента.
Это потому, что React знает, что функция кэшируется, а значит, и то же самое. Таким образом, если не было изменено какое-либо другое свойство, нет необходимости повторно визуализировать дочерний компонент.
// Дочерний компонент:
импортировать {memo} из "реагировать"
экспорт const CountChild = memo ((props) => {
console.log ('Отрисовка CountBox')
return
})
// Родительский компонент:
импортировать {useState, memo, useCallback} из 'react'
// Импортируем дочерний компонент
импортировать {CountChild} из './countChild'
export const App = memo (() => {
// Добавляем состояние для принудительного повторного рендеринга
const [count, setCount] = useState (0)
// ИЗМЕНЕНИЕ: Запоминание обработчика кнопки:
const onCountClick = useCallback (() => {
setCount ((prevCount) => ++ prevCount)
}, []) // Никакой зависимости не требуется
возвращение (
count: {count}
)
})
Заключение: мемоизация в React
Благодаря memo ()
useMemo ()
и useCallback ()
мемоизация в React довольно проста. С помощью этих инструментов мы можем сделать наши приложения React быстрее и лучше. Я надеюсь, что это руководство помогло вам понять, что такое мемоизация и как использовать мемоизацию в React для оптимизации ваших приложений React.
<! —
У вас есть какие-либо вопросы, рекомендации, мысли, советы или подсказки, которыми вы хотели бы поделиться с другими читателями этого блога и со мной? Пожалуйста, поделитесь этим в комментарии. Вы также можете отправить мне письмо. Я хотел бы получить известие от вас.
->
<! —
Хотите еще?
->
Если вам понравилась эта статья, подпишитесь, чтобы не пропустить ни одного сообщения в будущем.
<! —
Кроме того, вы можете найти меня на GitHub, Twitter и Dribbble.
->
Если вы хотите поддержать меня и этот блог, вы можете стать его покровителем или купить мне кофе 🙂
Стать покровителем
Пожертвовать через Paypal