Мемоизация вычислений

Мемоизация в библиотеке Haunted предназначена для оптимизации вычислений внутри функциональных веб-компонентов. Библиотека предоставляет хук useMemo, позволяющий кешировать результат вычисления до тех пор, пока набор зависимостей не изменится. При правильном применении уменьшается количество повторных вычислений и перерасчётов шаблона, что особенно заметно при работе с массивами данных, генерацией виртуальных списков и сложной бизнес-логикой.

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

Ключевой момент: мемоизация не предотвращает повторный рендер компонента, но снижает стоимость вычислений при рендере, когда тяжёлые операции заменяются чтением значения из кеша.

Пример базового использования

useMemo применим, когда вычисление является детерминированным и зависит от входных параметров:

import { component, useState, useMemo } from 'haunted';

function Stats() {
  const [items, setItems] = useState([]);
  const total = useMemo(
    () => items.reduce((sum, i) => sum + i.value, 0),
    [items]
  );

  return html`<div>Сумма: ${total}</div>`;
}

customElements.define('app-stats', component(Stats));

В примере reduce выполняется только при изменении массива items, а не на каждом рендере.

Формирование производных данных

Помимо численных значений мемоизация удобна для формирования производных структур, например, сортированных или отфильтрованных массивов:

const sorted = useMemo(
  () => data.slice().sort((a, b) => a.score - b.score),
  [data]
);

Без мемоизации сортировка выполнялась бы при каждом обновлении интерфейса, даже если data не менялась.

Кеширование сложных вычислений

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

Практическое правило: если вычисление явно тяжелее, чем проверка зависимостей, его стоит мемоизировать.

Избежание лишних зависимостей

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

  • вынесение функций вне компонента;
  • использование useCallback для мемоизации колбэков;
  • формирование зависимостей из примитивных значений.

Взаимодействие с виртуальными списками

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

const virtualRows = useMemo(
  () => buildVirtualRows(data, viewport),
  [data, viewport]
);

Взаимодействие с рендерингом шаблонов

Хотя useMemo не мемоизирует шаблоны напрямую, через него можно формировать промежуточные данные: отфильтрованные коллекции, список свойств, результаты вычислений. Это уменьшает количество диффов, выполняемых движком рендеринга, так как структура данных остаётся стабильной.

Выбор между useMemo и useCallback

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

Практическая рекомендация: если результат вычисления не является функцией, корректный выбор — useMemo.

Сколько вычислений нужно мемоизировать

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

  1. выполняются часто;
  2. затратны по времени;
  3. зависят от небольшого набора данных;
  4. не меняют внешнее состояние.

Семантика зависимости и чистота функции

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

Кэш на уровне компонента

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

Применение в композиции хуков

useMemo органично сочетается с другими хуками Haunted. Например, вычисление зависимости перед установкой состояния делает обновления более предсказуемыми:

const filtered = useMemo(() => items.filter(x => x.visible), [items]);
const count = useMemo(() => filtered.length, [filtered]);

Каскад мемоизированных вычислений упрощает отслеживание изменений и снижает нагрузку.

Работа с рекурсивными или цепочечными вычислениями

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

Итоговое свойство мемоизации

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