Гид по технологиям

Мемоизация в JavaScript и React — как и когда использовать

6 min read Разработка Обновлено 13 Apr 2026
Мемоизация в JavaScript и React — как и когда
Мемоизация в JavaScript и React — как и когда

Чип на печатной плате в чёрно-жёлтых тонах

Что такое мемоизация

Мемоизация — это техника оптимизации, похожая на кэширование. Вместо повторного выполнения вычисления функция сохраняет результат для набора входных аргументов и возвращает сохранённый результат при следующем вызове с теми же аргументами. Ключевые свойства:

  • Работает лучше всего для чистых функций — функций, которые при одинаковых входных данных всегда возвращают один и тот же результат и не имеют побочных эффектов.
  • Экономит время выполнения в обмен на использование памяти для кэша.
  • Нужна осторожность с изменяемыми объектами, асинхронностью и побочными эффектами.

Определение терминов: чистая функция — возвращает одинаковый результат для одинаковых аргументов и не изменяет внешнее состояние.

Когда мемоизация полезна

  • Функция выполняет тяжёлые вычисления (поиск, сложная математика, парсинг, рекурсивные алгоритмы).
  • Один и тот же набор аргументов вызывается многократно.
  • Вы можете идентифицировать аргументы как надёжные ключи (примитивы или стабильные сериализуемые объекты).

Важно: если функция быстрая или параметры редко повторяются, мемоизация может только добавить накладные расходы на управление кэшем.

Простая мемоизация в JavaScript

Простейший подход — объект как кэш с ключами-строками.

function memoizeSimple(fn) {
  const cache = {};
  return function (...args) {
    const key = JSON.stringify(args);
    if (key in cache) {
      return cache[key];
    }
    const result = fn.apply(this, args);
    cache[key] = result;
    return result;
  };
}

// Пример
function square(n) {
  return n * n;
}
const memoizedSquare = memoizeSimple(square);
// memoizedSquare(5) вернёт 25 и сохранит результат в кэше

Замечания:

  • JSON.stringify безопасен для простых аргументов, но для функций, DOM-узлов или циклических объектов он не подойдёт.
  • Объект-как-кэш не даёт слабых ссылок; если ключи — большие объекты, это может привести к утечке памяти.

Поддержка объектов и слабых ссылок

Если аргумент — объект, лучше использовать Map или WeakMap. WeakMap хранит ссылки, которые не препятствуют сборщику мусора.

function memoizeWithWeakMap(fn) {
  const cache = new WeakMap();
  return function (obj) {
    if (!isObject(obj)) return fn(obj);
    if (cache.has(obj)) return cache.get(obj);
    const result = fn(obj);
    cache.set(obj, result);
    return result;
  };
}

function isObject(x) {
  return x !== null && (typeof x === 'object' || typeof x === 'function');
}

Ограничение: WeakMap можно использовать только если ключи — объекты, и вы не можете перебирать содержимое WeakMap.

LRU-кэш для контроля памяти

При ограниченной памяти применяют LRU (least recently used) — сбрасывать наименее используемые записи.

class LRUCache {
  constructor(limit = 100) {
    this.limit = limit;
    this.map = new Map();
  }
  get(key) {
    if (!this.map.has(key)) return undefined;
    const value = this.map.get(key);
    this.map.delete(key);
    this.map.set(key, value); // перемещаем в конец как недавно использованный
    return value;
  }
  set(key, value) {
    if (this.map.has(key)) this.map.delete(key);
    else if (this.map.size === this.limit) this.map.delete(this.map.keys().next().value);
    this.map.set(key, value);
  }
}

LRU хорош для серверных приложений и библиотек, где нужно ограничивать память.

Мемоизация в React — основные инструменты

React предоставляет механизмы мемоизации, которые помогают снизить число лишних вычислений и повторных рендеров:

  • useMemo — мемоизирует значение между рендерами.
  • useCallback — мемоизирует саму функцию.
  • React.memo — мемоизация компонентa (HOC), предотвращающая повторный рендер при одинаковых пропсах.

Общее правило: мемоизируйте с целью уменьшить реальные затраты; профилируйте и измеряйте.

useMemo

useMemo принимает фабрику значения и массив зависимостей. Оно возвращает кешированное значение до тех пор, пока зависимости не изменятся.

import { useMemo } from 'react';

function App({ value }) {
  const square = (n) => n * n;

  const result = useMemo(() => square(value), [value]);

  return (
    
{result}
); }

Советы:

  • Не используйте useMemo ради «предположительной» оптимизации — измерьте.
  • useMemo не гарантирует вечной мемоизации: React может очищать кеш между рендерами в некоторых реализациях.

React.memo

React.memo оборачивает компонент и выполняет поверхностное сравнение пропсов по умолчанию. Можно передать свою функцию сравнения.

const Comment = ({ name, comment, likes }) => (
  

{name}

{comment}

{likes}

); const MemoizedComment = React.memo(Comment); // С пользовательской функцией сравнения function areEqual(prevProps, nextProps) { return prevProps.likes === nextProps.likes && prevProps.comment === nextProps.comment && prevProps.name === nextProps.name; } const MemoizedCommentCustom = React.memo(Comment, areEqual);

Когда использовать React.memo:

  • Компонент рендерится часто из-за изменения родительского состояния.
  • Пропсы — простые и сравнительно дешёвые для сравнения.

Когда не использовать:

  • Компонент маленький и рендер очень быстрый.
  • Пропсы часто меняются и сравнение стоит дороже, чем повторный рендер.

useCallback

useCallback возвращает мемоизированную версию функции, которая сохраняет тот же идентификатор между рендерами, если зависимости не поменялись. Это удобно, когда функцию передают в дочерние компоненты или в зависимости useEffect.

import { useCallback, useEffect } from 'react';

const Component = () => {
  const getData = useCallback(() => {
    console.log('call an API');
  }, []);

  useEffect(() => {
    getData();
  }, [getData]);
};

Замечание: useCallback похож на useMemo, но он возвращает функцию, тогда как useMemo — значение.

Практические правила и эвристики

  • Правило 1: сначала измерьте. Профилирование — главный инструмент. Измеряйте CPU и время рендера.
  • Правило 2: мемоизируйте чистые и дорогие вычисления.
  • Правило 3: избегайте мемоизации на уровне каждой мелкой функции — это увеличит сложность и потребление памяти.
  • Правило 4: используйте WeakMap для кэша с объектными ключами, LRU для ограниченной памяти.
  • Правило 5: в React мемоизируйте компоненты и функции, только если они действительно вызывают лишние рендеры или пересчёты.

Ментальные модели:

  • “Время ↔ Память”: мемоизация платит временем (снижение повторных вычислений) памятью (кэш).
  • “Чем дороже вычисление — тем более оправдана мемоизация”.

Когда мемоизация не помогает (контрпример)

  • Функция возвращает случайные значения, зависит от времени или глобального состояния — мемоизация ломает логику.
  • Аргументы функции неизмеримы или часто уникальны (например, объекты с уникальными id) — кэш не попадёт повторно.
  • Если сравнение аргументов/пропсов дороже самого рендера или вычисления, то выигрыш нулевой или отрицательный.

Мини-методология внедрения мемоизации в проект

  1. Профилирование: найдите “горячие” функции и компоненты.
  2. Валидация чистоты: подтвердите, что функция чистая или её поведение допускает кэширование.
  3. Выбор стратегии: простой объект, Map/WeakMap или LRU.
  4. Тестирование: добавьте unit-тесты и тесты производительности.
  5. Мониторинг: следите за использованием памяти и временем ответа.
  6. Роллбек: подготовьте план отката, если оптимизация ухудшит поведение.

Контроль качества: критерии приёмки

  • Производительность: время отклика или время рендера упало в сравнении с базовой линией.
  • Корректность: результаты функций не отличаются от эталонных при всех тестовых наборах.
  • Память: нет роста потребления памяти вне ожидаемых границ.
  • Тесты: покрытие unit-тестами и интеграционными сценариями.

Ролевые чек-листы

Разработчик:

  • Измерил производительность до изменений.
  • Убедился в чистоте функции или в допустимости кэширования.
  • Выбрал подходящий тип кэша и ограничил размер, при необходимости.
  • Написал тесты и документацию.

Код-ревьюер:

  • Проверил измерения и тесты.
  • Убедился, что мемоизация не скрывает баги побочных эффектов.
  • Оценил безопасность по памяти и сборке мусора.

Операции/DevOps:

  • Наблюдение за метриками памяти и задержки.
  • План отката при деградации.

Тест-кейсы и приёмочные критерии

  • Вызов функции с одинаковыми аргументами возвращает тот же результат и использует кэш при повторных вызовах.
  • Вызов с разными аргументами возвращает корректные результаты и не возвращает чужие кэшированные значения.
  • Кэш очищается/сбрасывается при ожидании (если предусмотрено) и не приводит к утечкам.

Decision flowchart (простая схема принятия решения)

flowchart TD
  A[Есть проблема производительности?] -->|Нет| B[Не мемоизировать]
  A -->|Да| C[Функция чистая?]
  C -->|Нет| B
  C -->|Да| D[Часто повторяются те же аргументы?]
  D -->|Нет| B
  D -->|Да| E[Выбрать кэш: Object/Map/WeakMap/LRU]
  E --> F[Внедрить, протестировать, мониторить]

Безопасность и приватность

  • Не храните в кэше чувствительные данные без контроля времени жизни и доступа.
  • В браузере учитывайте ограничения памяти и поведение сборщика мусора.

Совместимость и замечания по версиям React

  • React.memo, useMemo и useCallback доступны в React 16.8+ (функциональные хуки).
  • React 18 предложил новые API (useId, useTransition и др.) для улучшения UX и управления конкурентными обновлениями; мемоизация остаётся релевантной, но часто узконаправленной.

Примеры, когда лучше альтернативы

  • Если вычисление можно сделать лениво и по частям — рассмотрите генераторы или стримы.
  • Если задача IO-bound — используйте кэширование на уровне сети/серверов (CDN, HTTP caching) вместо локальной мемоизации.

Краткое резюме

Мемоизация полезна для ускорения повторяющихся дорогих вычислений, но она требует дисциплины: профильте, подтверждайте чистоту функций, выбирайте подходящий тип кэша и контролируйте использование памяти. В React используйте useMemo, useCallback и React.memo целенаправленно — не как универсальный рецепт.

Важно: начинайте с измерений и возвращайтесь к реализации при изменениях поведения приложения или данных.

Часто задаваемые вопросы

Стоит ли мемоизировать всё подряд?

Нет. Мемоизируйте только после доказанного профита: измерений и анализа. Переизбыточная мемоизация усложняет код и может ухудшить производительность.

Помогает ли useMemo для ререндеров дочерних компонентов?

useMemo мемоизирует значение. Для предотвращения лишних ререндеров дочерних компонентов чаще используют useCallback (для функций) и React.memo (для компонентов).


Сводка:

  • Мемоизация = время за память.
  • Мемоизируйте чистые и дорогие вычисления.
  • В React используйте инструменты выборочно и измеряйте эффект.
Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

lsblk в Linux — обзор и примеры команд
Linux

lsblk в Linux — обзор и примеры команд

Стриминговые приложения на PS4 — как установить
Гайды

Стриминговые приложения на PS4 — как установить

Сменить пароль root в Kali Linux
Linux

Сменить пароль root в Kali Linux

Как пожаловаться в Twitter — пошаговое руководство
Социальные сети

Как пожаловаться в Twitter — пошаговое руководство

Открыть ISO, TAR и 7‑Zip на Chromebook
Инструкции

Открыть ISO, TAR и 7‑Zip на Chromebook

Как архивировать и распаковывать файлы в Windows
Windows

Как архивировать и распаковывать файлы в Windows