Хук useMemo в React Native мемоизирует результат вычислений между рендерами компонента, предотвращая повторное выполнение дорогостоящих операций. В отличие от useCallback, который мемоизирует функцию, useMemo сохраняет возвращаемое значение и пересчитывает его только при изменении указанных зависимостей. По данным React Docs, 2024, мемоизация особенно эффективна для вычислений с массивами большого размера и сложных преобразований данных. В React Native каждый лишний ререндер создаёт дополнительную нагрузку на мостовое соединение между JavaScript и нативным потоками, поэтому правильное использование useMemo напрямую влияет на плавность анимаций и скорость отклика интерфейса.
Главное
useMemo — это хук из стандартной библиотеки React, доступный в React Native без дополнительных пакетов. Он мемоизирует значение, возвращаемое переданной функцией, и переиспользует это значение на последующих рендерах до тех пор, пока не изменятся указанные в массиве зависимости.
Название хука образовано от английского memoization — техники оптимизации, при которой результат дорогостоящей функции сохраняется в кеше. При повторном вызове с теми же аргументами возвращается сохранённое значение вместо повторного вычисления.
В контексте React Native мемоизация особенно важна из-за особенностей архитектуры. Каждый ререндер компонента отправляет данные через мост JavaScript — Нативный, что занимает время. Если компонент содержит дорогостоящие вычисления (фильтрация списка, сортировка, форматирование), каждый ререндер будет повторять их, блокируя JS-поток и вызывая дроп фреймов.
По данным React Native Performance Docs, 2024, узким местом чаще всего оказывается не рендеринг нативных компонентов, а выполнение JavaScript-логики между рендерами. useMemo решает именно эту проблему.
useMemo принимает функцию и массив зависимостей, возвращая мемоизированное значение. React хранит предыдущее значение и массив зависимостей. При каждом рендере React сравнивает текущие зависимости с предыдущими с помощью Object.is. Если хотя бы одна зависимость изменилась, функция выполняется заново, и результат сохраняется.
Сигнатура useMemo полностью идентична на вебе и в React Native. Первый аргумент — функция без параметров, возвращающая мемоизируемое значение. Второй — массив зависимостей, при изменении которых значение будет перевычислено.
import React, { useMemo } from 'react';
const sortedList = useMemo(() => {
return items.sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
В этом примере sortedList пересчитывается только когда изменяется ссылка на массив items. Если массив изменился по содержимому, но ссылка осталась той же (мутация), useMemo не увидит изменений — это важное ограничение.
React использует для сравнения зависимостей алгоритм Object.is, который работает аналогично строгому равенству ===, но корректно обрабатывает NaN (+0 и -0 считаются разными). Это значит, что для примитивных типов (строки, числа, булевы) сравнение работает по значению, а для объектов и массивов — по ссылке.
// Object.is({'a': 1}, {'a': 1}) -> false (different refs)
// Object.is(42, 42) -> true (primitive by value)
// Object.is(NaN, NaN) -> true (correct handling of NaN)
Понимание этого механизма критично: если передавать в зависимостях новый объект при каждом рендере, мемоизация будет бесполезной, так как зависимости будут считаться изменившимися каждый раз.
useMemo оправдан в трёх сценариях: дорогостоящие вычисления, передача стабильных пропсов дочерним компонентам, и сохранение ссылочной идентичности объектов. В React Native все три сценария встречаются регулярно из-за особенностей архитектуры мобильных приложений.
Если компонент выполняет обработку больших массивов данных (фильтрация, сортировка, группировка) или ресурсоёмкие математические операции, useMemo предотвращает повтор этих вычислений при каждом ререндере. В мобильных приложениях типичный пример — форматирование данных из API перед отображением в списке.
Когда дочерний компонент обёрнут в React.memo, он перерендеривается только при изменении пропсов. Если пропс-объект создаётся внутри родителя без useMemo, при каждом рендере родителя будет создаваться новая ссылка, и дочерний компонент будет рендериться заново, сводя пользу React.memo к нулю.
В React Native стили часто зависят от пропсов — например, ширина элемента вычисляется на основе размера экрана. useMemo позволяет вычислять такие динамические стили только при изменении входных параметров.
useMemo не является универсальным средством оптимизации. В ряде сценариев он не даёт выигрыша и даже может ухудшить производительность из-за накладных расходов на хранение и сравнение зависимостей. В React Native, где каждый байт памяти важен, слепое применение useMemo без измерения эффекта — антипаттерн.
Хук не приносит пользы в трёх основных случаях. Во-первых, если вычисление тривиально (простое сложение, конкатенация строк), затраты на useMemo превышают стоимость самого вычисления. Во-вторых, если зависимости меняются каждый рендер — функция всё равно будет выполняться, а мемоизация добавит лишь лишнюю работу. В-третьих, если компонент рендерится редко, оверхед от хранения кеша не оправдан.
По данным Kent C. Dodds, 2023, прежде чем добавлять useMemo, нужно измерить проблему с помощью React DevTools Profiler или встроенного Performance Monitor в React Native. Если ререндер компонента занимает меньше 1 мс, мемоизация не нужна.
useMemo и useCallback — два хука мемоизации из React, которые часто путают. Разница в том, что именно они сохраняют: useMemo возвращает результат выполнения функции (любое значение), а useCallback возвращает саму функцию. Оба принимают массив зависимостей, но синтаксис различается.
| Характеристика | useMemo | useCallback |
|---|---|---|
| Возвращает | Результат выполнения функции | Ссылку на функцию |
| Первый аргумент | Функция, возвращающая значение | Функция, которую нужно мемоизировать |
| Типичное применение | Кеширование вычислений | Стабильные колбэки для дочерних компонентов |
| Эквивалентность | useMemo(() => fn, deps) | useCallback(fn, deps) === useMemo(() => fn, deps) |
На практике useCallback — это синтаксический сахар над useMemo для случая, когда нужно мемоизировать функцию. В React Native оба хука применяются одинаково часто: useMemo — для данных (отформатированный текст, вычисленные стили), useCallback — для обработчиков событий (onPress, onChangeText).
Рассмотрим два реальных сценария использования useMemo в React Native приложениях. Первый — фильтрация списка контактов по поисковому запросу, второй — вычисление динамических стилей на основе размера экрана.
В этом примере useMemo предотвращает повторную фильтрацию большого списка контактов при каждом рендере, если не изменился ни поисковый запрос, ни исходные данные.
const ContactList = ({ contacts, searchQuery }) => {
const filteredContacts = useMemo(() => {
if (!searchQuery.trim()) return contacts;
const query = searchQuery.toLowerCase();
return contacts.filter((c) =>
c.name.toLowerCase().includes(query) ||
c.phone.includes(query)
);
}, [contacts, searchQuery]);
return (
<FlatList
data={filteredContacts}
renderItem={renderContact}
keyExtractor={(item) => item.id}
/>
);
};
Без useMemo фильтрация выполнялась бы на каждый рендер, включая те, что вызваны анимациями или изменением состояния других частей компонента. С useMemo фильтрация запускается только при изменении contacts или searchQuery.
В React Native стили часто зависят от размеров экрана или пропсов компонента. useMemo позволяет вычислять такой объект стилей один раз и переиспользовать, пока зависимости не изменятся.
const Card = ({ title, isSelected }) => {
const cardStyle = useMemo(() => ({
backgroundColor: isSelected ? '#e3f2fd' : '#ffffff',
borderWidth: isSelected ? 2 : 1,
padding: 16,
}), [isSelected]);
return (
<View style={cardStyle}>
<Text>{title}</Text>
</View>
);
};
Этот подход гарантирует, что объект cardStyle остаётся стабильным (та же ссылка), пока isSelected не изменится. Если Card обёрнут в React.memo, такой паттерн предотвращает лишние ререндеры дочерних элементов.
Часто задаваемые вопросы
useMemo возвращает мемоизированное значение — результат выполнения функции, переданной первым аргументом. При последующих рендерах React возвращает сохранённое значение, если зависимости не изменились.
useMemo возвращает результат выполнения функции (любое значение: число, объект, JSX), а useCallback возвращает саму функцию. При этом useCallback(fn, deps) эквивалентен useMemo(() => fn, deps).
useMemo бесполезен при тривиальных вычислениях (сложение, конкатенация), редких ререндерах, изменяющихся на каждом рендере зависимостях и для примитивных типов. Всегда измеряйте производительность перед добавлением useMemo.
Да, если использовать его без необходимости. React хранит предыдущие значения в памяти и выполняет сравнение зависимостей на каждом рендере. Для простых вычислений оверхед от этих операций превышает выигрыш от мемоизации.
Используйте React DevTools Profiler для замера времени рендера компонентов. В React Native также доступен Performance Monitor через меню разработчика. Если ререндер занимает менее 1 мс — useMemo не нужен.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также