useMemo и useCallback для оптимизации

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

useMemo: мемоизация вычисленных значений

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

import { useMemo } from 'preact/hooks';

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • Первый аргумент — функция, возвращающая значение, которое нужно мемоизировать.
  • Второй аргумент — массив зависимостей. Значение пересчитывается только если одна из зависимостей изменилась.

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

const numbers = [1, 2, 3, 4, 5];
const sum = useMemo(() => {
    console.log('Вычисляем сумму...');
    return numbers.reduce((acc, num) => acc + num, 0);
}, [numbers]);

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

Особенности и советы по useMemo:

  • Не использовать для микровычислений — мемоизация имеет накладные расходы.
  • Полезен при фильтрации, сортировке массивов, сложных математических вычислениях.
  • Гарантирует, что ссылка на объект или массив останется прежней при неизменных зависимостях, что важно для оптимизации дочерних компонентов через memo.

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

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

import { useCallback } from 'preact/hooks';

const memoizedHandler = useCallback(() => {
    console.log('Обработчик вызван');
}, [dependency]);
  • Функция пересоздаётся только при изменении зависимостей.
  • Часто используется для передачи функций в дочерние компоненты, обернутые в memo, чтобы избежать ненужного рендера.

Пример:

const Button = ({ onClick }) => (
    <button onCl ick={onClick}>Нажми</button>
);

const App = () => {
    const [count, setCount] = useState(0);

    const increment = useCallback(() => {
        setCount(prev => prev + 1);
    }, []);

    return (
        <>
            <Button onCl ick={increment} />
            <p>Счёт: {count}</p>
        </>
    );
};

Без useCallback функция increment создавалась бы заново при каждом рендере, что могло бы вызывать лишние рендеры дочернего компонента Button, если он обёрнут в memo.

Отличия и сходства useMemo и useCallback

Хук Возвращаемое значение Использование
useMemo Вычисленное значение Кэширование результатов вычислений, массивов, объектов
useCallback Функция Кэширование функций для передачи в дочерние компоненты

Фактически, useCallback(fn, deps) эквивалентен useMemo(() => fn, deps), но предназначен для повышения читаемости кода.

Практические сценарии применения

  1. Оптимизация сложных вычислений:
const filteredData = useMemo(() => {
    return data.filter(item => item.active).map(item => item.name);
}, [data]);
  1. Передача коллбэков в memo-компоненты:
const Child = memo(({ onAction }) => {
    console.log('Рендер Child');
    return <button onCl ick={onAction}>Action</button>;
});

const Parent = () => {
    const [value, setValue] = useState(0);

    const handleAction = useCallback(() => setValue(v => v + 1), []);

    return <Child onAct ion={handleAction} />;
};
  1. Избежание ненужного пересоздания объектов и массивов:
const options = useMemo(() => [{ label: 'A', value: 1 }, { label: 'B', value: 2 }], []);

Подводные камни и рекомендации

  • Не использовать без необходимости: мемоизация имеет свои накладные расходы.
  • Следить за массивом зависимостей: пропущенные зависимости могут привести к багам.
  • Для частых изменений состояний мемоизация может быть менее эффективной, чем прямое вычисление.
  • Объекты и массивы — мемоизировать желательно, чтобы дочерние компоненты не перерендеривались из-за изменения ссылок.

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