Связь с useMemo

Haunted — это современная библиотека для создания веб-компонентов на базе стандартов Custom Elements, использующая функциональный подход, схожий с React Hooks. Одним из ключевых инструментов оптимизации производительности в Haunted является useMemo, который позволяет кэшировать вычисления и предотвращать лишние перерасчёты при каждом рендере компонента.


Основы useMemo

useMemo — это hook, предназначенный для мемоизации дорогих вычислений, зависящих от конкретных значений. Его сигнатура в Haunted выглядит следующим образом:

const memoizedValue = useMemo(fn, deps);
  • fn — функция, результат выполнения которой необходимо мемоизировать.
  • deps — массив зависимостей, при изменении которых fn будет выполнена заново.

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


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

Рассмотрим компонент, который генерирует список чисел с дорогостоящей обработкой:

import { html, useMemo } from 'haunted';

function NumberList({ numbers }) {
  const processedNumbers = useMemo(() => {
    console.log('Вычисление списка');
    return numbers.map(n => n * 2).filter(n => n % 3 === 0);
  }, [numbers]);

  return html`
    <ul>
      ${processedNumbers.map(n => html`<li>${n}</li>`)}
    </ul>
  `;
}

Разбор примера:

  1. useMemo получает функцию, которая выполняет обработку массива numbers.
  2. Функция будет вызвана только при изменении массива numbers.
  3. Если компонент ререндерится без изменения numbers, вычисление не происходит, экономя ресурсы.

Отличия useMemo в Haunted от React

  1. Малый размер библиотеки. Haunted не включает виртуальный DOM, поэтому мемоизация выполняется без лишней нагрузки на дерево компонентов.

  2. Прямое использование в Custom Elements. Мемои́зация работает в контексте Shadow DOM и не зависит от виртуальной структуры.

  3. Поведение при undefined или пустых зависимостях:

    const value = useMemo(() => compute(), []); // вычисление выполнится один раз
    const value2 = useMemo(() => compute());    // вычисление выполнится на каждый рендер

    Без массива зависимостей useMemo работает как обычное вычисление при каждом рендере.


Комбинация с другими Hooks

useMemo часто используется вместе с useState и useEffect для предотвращения лишних эффектов:

import { html, useMemo, useState, useEffect } from 'haunted';

function FilteredList({ items }) {
  const [filter, setFilter] = useState('');

  const filtered = useMemo(() => {
    return items.filter(item => item.includes(filter));
  }, [items, filter]);

  useEffect(() => {
    console.log('Фильтр обновлён');
  }, [filtered]);

  return html`
    <input type="text" @input=${e => setFilter(e.target.value)} />
    <ul>
      ${filtered.map(item => html`<li>${item}</li>`)}
    </ul>
  `;
}

Особенности:

  • Мемои́зация filtered предотвращает лишние вызовы useEffect.
  • При каждом изменении filter или items происходит пересчёт только filtered, а не всего компонента.

Рекомендации по использованию

  • useMemo стоит применять только для дорогих вычислений, иначе накладные расходы на хранение кэша могут превысить выигрыш.
  • Для простых операций фильтрации, маппинга или вычислений, не требующих значительных ресурсов, его использование не оправдано.
  • Следует внимательно управлять массивом зависимостей. Любое изменение, которое не учтено, может привести к устаревшему кэшу или лишним пересчётам.

Особые случаи

  1. Комплексные объекты и массивы как зависимости В Haunted useMemo проверяет зависимости по ссылке. Изменение даже одного элемента в массиве или объекте приведёт к пересчёту:

    const memo = useMemo(() => compute(arr), [arr]);
    arr.push(5); // useMemo не сработает без создания новой ссылки: useMemo(() => compute(arr), [[...arr]])
  2. Встроенные функции и стрелочные выражения При передаче функций в зависимости необходимо создавать стабильные ссылки через useCallback или выносить их за пределы рендера, чтобы мемоизация работала корректно.


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