Gernar
Frontend DeveloperReact и производительность

Интервью-вопрос

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

Мемоизация хранит результат или ссылку, чтобы при тех же входных данных не делать лишнюю работу. На интервью важно показать не только инструменты React, но и границы. Кэш стоит памяти, зависит от ссылок и может дать устаревшие данные при ошибочных зависимостях.

Добавлен
Редакция

Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.

🐰0
🥚0

Мини-квиз

Проверка перед разбором

Несколько быстрых вопросов перед разбором. Так проще поймать места, которые только кажутся понятными.

Вопрос 1 из 60 правильно

Как лучше сказать, что такое мемоизация?

Вы отвечаете коротко и хотите сразу показать практический смысл.

Варианты ответа

Разбор

Разобраться, а не зазубрить

Дальше разбираем суть, типичные уточнения и места, где легко сказать лишнее или перепутать термины.

Базовая идея

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

Во frontend вы встретите эту идею в двух местах. Первое, тяжелые вычисления в JavaScript: фильтрация большого массива, сортировка, построение derived data. Второе, React-рендеры: если props не изменились, можно не пересчитывать компонент или не создавать новую ссылку без причины.

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

Короткий ответ на интервью

Можно ответить так:

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

Такой ответ звучит практично. Вы показываете принцип, инструменты и ограничение. Это лучше, чем просто перечислить React.memo, useMemo и useCallback без объяснения, когда они действительно помогают.

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

Как решить, нужна ли мемоизация

1Вычисление заметно тяжелое или работает на большом списке?
Рассмотрите useMemo, но проверьте зависимости и измерьте эффект.
2Дочерний компонент обернут в React.memo и получает callback или объект?
Стабилизируйте ссылку через useCallback или useMemo, если это действительно снижает лишние рендеры.
3Значение дешевое и не передается в дочерний компонент с React.memo?
Не мемоизируйте. Простое вычисление часто дешевле кэша и сравнения зависимостей.
4Кэш может расти от разных аргументов?
Добавьте лимит, очистку или другой механизм. Иначе получите лишнее потребление памяти.

Главный критерий простой: есть ли повторная работа, которую дорого делать снова. Если компонент простой, вычисление занимает микросекунды, а результат не передается в дочерний компонент с React.memo, мемоизация может быть шумом.

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

Где мемоизация опасна

Не используйте мемоизацию как способ управлять побочными эффектами. useMemo выполняется во время рендера и должен возвращать значение. Если положить туда fetch, подписку или запись во внешнее хранилище, можно получить лишний запрос, гонку ответов, зависший loading или обновление state после размонтирования.

Безопаснее держать сетевую логику в useEffect с полной зависимостью от параметров запроса, обработкой loading, error и cleanup. Если запрос можно отменить, добавьте отмену через AbortController или механизм вашего слоя данных.

Инструменты React

В React важно не смешивать инструменты:

  • React.memo оборачивает компонент и позволяет пропустить рендер, если props не изменились.
  • useMemo возвращает мемоизированное значение.
  • useCallback возвращает мемоизированную функцию.

Пример с derived data:

function ProductList({ products, query }) {
  const visibleProducts = useMemo(() => {
    return products.filter((product) =>
      product.title.toLowerCase().includes(query.toLowerCase())
    );
  }, [products, query]);

  return <List items={visibleProducts} />;
}

Здесь результат фильтрации пересчитывается только при изменении products или query. Если забыть query в зависимостях, UI может показывать старый список после ввода нового текста.

Главная ловушка со ссылками

Осознанная оптимизация
  1. 1Находите дорогой рендер или вычисление
  2. 2Проверяете, какие props меняются по ссылке
  3. 3Стабилизируете только нужные значения
  4. 4Смотрите результат в профайлере или метриках
Опасный путь
  1. 1Оборачиваете все в useMemo и useCallback
  2. 2Забываете зависимость ради тишины линтера
  3. 3Передаете новые объекты в React.memo
  4. 4Получаете сложный код без заметного ускорения

React.memo особенно часто не дает эффекта из-за новых ссылок. Объект, массив или функция, созданные внутри render, получают новую ссылку на каждом рендере. Для React это новый prop.

Плохой пример:

const UserCard = React.memo(function UserCard({ user, actions }) {
  return <button onClick={actions.onSelect}>{user.name}</button>;
});

function Page({ user }) {
  return (
    <UserCard
      user={user}
      actions={{ onSelect: () => console.log(user.id) }}
    />
  );
}

Здесь actions каждый раз новый, поэтому UserCard видит изменившийся prop. На практике это дает лишние рендеры дочернего компонента и забирает пользу у React.memo. Лучше стабилизировать только то, что правда нужно дочернему компоненту:

const UserCard = React.memo(function UserCard({ user, onSelect }) {
  return <button onClick={() => onSelect(user.id)}>{user.name}</button>;
});

function Page({ user }) {
  const handleSelect = useCallback((id) => {
    console.log(id);
  }, []);

  return <UserCard user={user} onSelect={handleSelect} />;
}

Такой код не гарантирует, что ререндеров не будет вообще. Но он убирает одну частую причину лишних рендеров, новую ссылку на callback или объект.

Мемоизация вне React

В обычном JavaScript мемоизацию часто делают через замыкание и Map. Это удобно для чистых функций, где одинаковые аргументы дают одинаковый результат.

function memoizeOneArg(fn) {
  const cache = new Map();

  return (arg) => {
    if (cache.has(arg)) {
      return cache.get(arg);
    }

    const result = fn(arg);
    cache.set(arg, result);
    return result;
  };
}

Этот пример простой, но у него есть ограничение. Если аргументов много и они почти всегда разные, cache будет расти и не даст пользы. Для реального кэша нужно понимать ключи, срок жизни данных и момент очистки.

Практический вывод

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

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

Частые ошибки

Где обычно ошибаются

Проверьте формулировки, которые звучат уверенно, но на интервью быстро выдают пробелы.

  1. 1

    Мемоизировать все подряд

    Каждый кэш тоже стоит денег. React хранит значение и сравнивает зависимости. Если вычисление дешевое, код станет сложнее, а приложение не станет быстрее. Лучше сначала найти реальную проблему и мемоизировать только горячий участок.

  2. 2

    Забывать зависимости

    Если в useMemo или useCallback не указать зависимость, функция может работать со старым значением. Так появляются stale UI, неверные фильтры, старые id в обработчиках и баги, которые заметны только после нескольких действий пользователя.

  3. 3

    Ждать от React.memo полной защиты от ререндеров

    React.memo проверяет props, но не отменяет рендер из-за собственного state, context или изменившихся ссылок в props. Если каждый render родителя создает новый объект, мемоизация дочернего компонента почти не даст эффекта.

  4. 4

    Делать кэш без ограничения размера

    В ручной memoize-функции кэш может расти на каждом новом наборе аргументов. Для frontend это риск лишней памяти, особенно в долгоживущих вкладках. Безопаснее ограничивать размер, очищать кэш или использовать подходящий тип ключей.

  5. 5

    Прятать плохую архитектуру за мемоизацией

    Если компонент получает слишком много данных, часто меняет state или рендерит огромный список без виртуализации, мемоизация только маскирует проблему. Сначала сузьте props, разделите компонент или оптимизируйте список. Потом добавляйте кэш, если он все еще нужен.

  6. 6

    Запускать запросы внутри useMemo

    useMemo не предназначен для fetch, подписок и других побочных эффектов. Такой код может дать лишний запрос, гонку ответов или неверный loading. Безопаснее делать загрузку в useEffect с очисткой и отменой запроса либо вынести ее в слой работы с данными.

Follow-up

Что могут спросить дальше

Короткие ответы на вопросы, которыми проверяют понимание мемоизации, React-рендеров и цены кэша.

Живые ответы

Видео с похожим вопросом

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

Пока видео нет. Когда появятся подходящие публичные интервью, добавим их в этот блок, чтобы можно было сравнить разбор с тем, как отвечают реальные кандидаты.

Содержание