Мемоизация в React (useMemo, useCallback, React.memo)
Мемоизация
Определение
Мемоизация это процесс оптимизации, при котором результат сложных вычислений сохраняется отдельно в памяти для предотвращения повторного вычисления.
Мемоизация в React
При каждом ре-рендере компонент пересоздает переменные и функции объявленные внутри компонента. Хуки для мемоизации позволяют сохранять их между рендерами.
Хуки принимают аргументами значения для мемоизации и массив зависимостей, при изменении которых значение должно пересоздаваться.
При первом рендере хук с пустыми зависимостями создает мемоизированное значение.
При повторных рендерах, при наличии зависимостей, хук решает:
useCallback
Используется для мемоизации функций.
- Уменьшение количества повторных рендеров
- Предотвращение слишком частого срабатывания эффекта
- Оптимизация пользовательского хука
const prevState = {
callback: null,
deps: null
}
function useCallback(callback, deps) {
// если deps не существует в prevState
// или в новых данных, сохраняем
// текущие параметры и возвращем callback.
if (!prevState.deps || !deps) {
prevState.callback = callback
prevState.deps = deps
return callback
}
// Если deps существуют. Сравниваем
// какой-либо функцией массивы и если они совпадают,
// тогда возвращаем мемоизированную функцию
if (shallowEqual(deps, prevState.deps)) {
return prevState.callback
}
// Ну и если deps не совпадают, тогда снова
// сохраняем параметры и возвращаем текущий callback
prevState.callback = callback
prevState.deps = deps
return callback
}Функция для useCallback может принимать любые аргументы и возвращать любые значения (в отличие от useMemo, где функция принимать аргументы не должна).
Переменная или функция объявленая в родительском компоненте и проброшенная в дочерний — будет всегда считаться новой. Поэтому, кроме useCallback в родительском компоненте, следует использовать React.memo в дочернем, например:
import { memo } from 'react';
const List = memo(function List({ items }) {});
function TodoList({ todos, tab, theme }) {
// используйте useMemo, чтобы данные кэшировались
// между повторными рендерингами
// до тех пор, пока зависимости не изменятся...
const visibleTodos = useMemo(
() => filterTodos(todos, tab), [todos, tab]
);
// List получит одинаковые свойства и пропустит ре-рендер
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
}Потеря ссылки на мемоизированный объект так же отключает оптимизацию:
const Chart = memo(function Chart({onClick}) {
return (
<div onClick={onCLick}>...</div>
);
}
function ReportList({ items }) {
const handleClick = useCallback((item) => {
sendReport(item)
}, [item]);
return (
<article>{items.map(item => {
return (
<figure key={item.id}>
// такой вызов лишает мемоизацию всякого смысла
<Chart onClick={() => handleClick(item)} />
</figure>
);
})}
</article>
);
}useMemo
Позволяет сравнивать зависимости и кэшировать результаты вычислений между ре-рендерами (re-rendering).
Сравнение значений происходит при помощи Object.is()
- Уменьшение количества сложных вычислений
- Уменьшение количества повторных рендеров компонентов
- Мемоизация значения, которое используется в зависимостях другого хука
- Функция для мемоизации должна быть чистой, не принимать аргументов и всегда иметь возвращаемое значение
useMemoдолжен всегда содержать массив зависимостей, даже если он пустой. Без массива зависимостей он будет выполнятся при каждом рендере.
React.memo (hoc)
Принимает в себя компонент или функцию для ручного сравнения, которая решает обновлять компонент или нет.
Аналоги из классовых компонентов:
Рекомендация
Мемоизацию следует использовать только при наличии реальных проблем с производительностью: