Мемоизация данных

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


Хук useMemo

В Haunted используется хук useMemo, который служит аналогом React useMemo. Он принимает два аргумента:

  1. Функция вычисления значения – функция, результат которой нужно кэшировать.
  2. Массив зависимостей – список переменных, изменение которых приводит к пересчету.
import { html, component, useMemo } from 'haunted';

function ExpensiveComponent({ num }) {
  const factorial = useMemo(() => {
    console.log('Вычисление факториала');
    const calcFactorial = n => (n <= 1 ? 1 : n * calcFactorial(n - 1));
    return calcFactorial(num);
  }, [num]);

  return html`<p>Факториал числа ${num} равен ${factorial}</p>`;
}

customElements.define('expensive-component', component(ExpensiveComponent));

В этом примере вычисление факториала выполняется только при изменении num. Если значение num не меняется между рендерами, хук возвращает сохранённый результат, экономя ресурсы.


Принципы работы useMemo

  1. Зависимости определяют пересчёт: Если массив зависимостей пустой ([]), вычисление произойдёт только один раз при монтировании компонента.

  2. Функция вычисления выполняется лениво: useMemo не запускает функцию на каждом рендере, а только когда изменяются зависимости.

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


Использование мемоизации с вычисляемыми данными

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

function FilteredList({ items, filter }) {
  const filteredItems = useMemo(() => {
    console.log('Фильтрация списка');
    return items.filter(item => item.includes(filter));
  }, [items, filter]);

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

При изменении только filter пересчитывается фильтрованный список, но если список items остаётся неизменным, вычисления не повторяются.


Мемоизация функций с useCallback

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

import { useCallback } from 'haunted';

function Counter({ initial }) {
  const [count, setCount] = useState(initial);

  const increment = useCallback(() => setCount(c => c + 1), []);

  return html`
    <p>Счёт: ${count}</p>
    <button @click=${increment}>Увеличить</button>
  `;
}

increment сохраняет одну и ту же ссылку на функцию между рендерами, что предотвращает лишние пересоздания при передаче в дочерние компоненты.


Советы по эффективной мемоизации

  1. Избегать ненужной мемоизации: мемоизация имеет небольшой накладной расход на хранение кэша и сравнение зависимостей. Для лёгких вычислений она не требуется.

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

  3. Следить за зависимостями: всегда указывать все переменные, от которых зависит вычисляемое значение, иначе результат может устаревать.

  4. Ленивые вычисления: useMemo вычисляет значение только по необходимости, что уменьшает нагрузку при частых рендерах.


Примеры комплексного применения

Мемоизация нескольких вычислений одновременно:

function Dashboard({ data }) {
  const totalUsers = useMemo(() => data.length, [data]);
  const activeUsers = useMemo(() => data.filter(u => u.active).length, [data]);

  return html`
    <div>
      <p>Всего пользователей: ${totalUsers}</p>
      <p>Активные пользователи: ${activeUsers}</p>
    </div>
  `;
}

Сочетание с useCallback для оптимизации событий:

function Search({ items }) {
  const [query, setQuery] = useState('');

  const handleInput = useCallback(e => setQuery(e.target.value), []);

  const results = useMemo(() => items.filter(item => item.includes(query)), [items, query]);

  return html`
    <input type="text" @input=${handleInput} placeholder="Поиск..." />
    <ul>
      ${results.map(item => html`<li>${item}</li>`)}
    </ul>
  `;
}

Мемоизация в Haunted обеспечивает эффективное управление вычислениями, снижает нагрузку на DOM и CPU и позволяет создавать компоненты с высоким уровнем производительности. Сочетание useMemo и useCallback является стандартной практикой при работе с динамическими данными и сложными рендерами.