Мемоизация в 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)
Принимает в себя компонент или функцию для ручного сравнения, которая решает обновлять компонент или нет.
Аналоги из классовых компонентов:
Рекомендация
Мемоизацию следует использовать только при наличии реальных проблем с производительностью: