useCallbackCompare
Описание
useCallbackCompare — обёртка над useCallback, которая возвращает мемоизированный колбэк, обновляющийся только при «логическом» изменении зависимостей. Поддерживает три формы:
- массив зависимостей с поверхностным сравнением по индексам (
===), - кастомная функция сравнения и отдельное значение,
- только функция сравнения, если логика сравнения инкапсулирована внутри неё.
Сигнатура
ts
// 1) Массив зависимостей
function useCallbackCompare<CallbackType extends (...args: any) => any>(
callback: CallbackType,
deps: unknown[],
): CallbackType;
// 2) Кастомный компаратор + значение
function useCallbackCompare<
CallbackType extends (...args: any) => any,
ComparedValue,
>(
callback: CallbackType,
compare: (prev: ComparedValue, next: ComparedValue) => boolean,
comparedValue: ComparedValue,
): CallbackType;
// 3) Только компаратор
function useCallbackCompare<CallbackType extends (...args: any) => any>(
callback: CallbackType,
compare: () => boolean,
): CallbackType;Параметры
callback— функция, которую нужно мемоизировать.deps— массив зависимостей; сравнивается поверхностно по индексам (===).compare— функция сравнения, которая должна вернутьtrue, если значения равны (изменения нет), иfalse, если различаются (изменение есть).comparedValue— значение для сравнения пользовательским компаратором.
Возвращает
- Мемоизированный колбэк того же типа, что и
callback.
- Мемоизированный колбэк того же типа, что и
Примеры
1) Массив зависимостей: стабильный обработчик
tsx
import { useCallbackCompare } from '@webeach/react-hooks';
function AddToCart({ productId, qty }: { productId: string; qty: number }) {
const handleClick = useCallbackCompare(() => {
add(productId, qty);
}, [productId, qty]);
return <button onClick={handleClick}>Add</button>;
}2) Кастомный компаратор по ключу сущности
tsx
import { useCallbackCompare } from '@webeach/react-hooks';
function SaveButton({ user }: { user: { id: string; name: string } }) {
const onSave = useCallbackCompare(
() => updateUser(user),
(prev, next) => prev?.id === next?.id, // переопределять колбэк только если id поменялся
user,
);
return <button onClick={onSave}>Save</button>;
}3) Только компаратор: логика внутри функции
tsx
import { useCallbackCompare } from '@webeach/react-hooks';
// сравниваем по версии конфигурации из замыкания
function ApplyTheme({ config }: { config: ThemeConfig }) {
const apply = useCallbackCompare(
() => applyTheme(config),
() => config.version === lastAppliedVersion.current,
);
return <button onClick={apply}>Apply theme</button>;
}Поведение
Триггер обновления
- Колбэк переопределяется только при детектированном изменении зависимостей. Иначе сохраняется прежняя ссылочная идентичность.
Динамический массив зависимостей
- В форме с
depsмассив может быть полностью динамическим: длина и порядок могут меняться от рендера к рендеру. Сравнение учитывает как значения по индексам, так и длину; изменение длины тоже считается изменением. В отличие от стандартногоuseCallback, не требуется поддерживать «строго одинаковую» длину массива между рендерами.
- В форме с
Семантика компаратора
- Возвращайте
true, если значения равны (изменения нет), иfalse, если различаются (изменение есть). Это определяет, будет ли создан новый колбэк.
- Возвращайте
Стабильность ссылки
- При отсутствии изменений возвращается та же функция (по ссылке), что полезно для
React.memo, зависимостей эффектов и пропов дочерним компонентам.
- При отсутствии изменений возвращается та же функция (по ссылке), что полезно для
Когда использовать
- Передача обработчиков в
React.memo‑компоненты без лишних перерисовок. - Когда важны значимые изменения зависимостей (например, по
id), а не простая смена ссылок. - Интеграции, где требуется стабильная ссылка на колбэк (подписки, виртуализация, внешние виджеты).
Когда не использовать
- Если обычный
useCallbackс массивом зависимостей покрывает задачу. - Когда проще хранить данные в состоянии/рефе и читать их внутри стабильного колбэка.
- Если колбэк тяжёлый сам по себе — оптимизируйте его, а не только условия пересоздания.
Частые ошибки
Инвертированная логика компаратора
- Компаратор должен возвращать
trueпри равенстве иfalseпри различии. Иная семантика приведёт к лишним/пропущенным пересозданиям колбэка.
- Компаратор должен возвращать
Залежалые замыкания
- Если колбэк использует значения, которые не участвуют в сравнении (
deps/comparedValue/compare), он может читать устаревшие данные. Добавьте эти значения в сравнение или читайте их изref.
- Если колбэк использует значения, которые не участвуют в сравнении (
Нестабильный
comparedValue- В форме с компаратором следите, чтобы
comparedValueне пересоздавался по пустякам (новые ссылки без смыслового изменения). Иначе колбэк будет пересоздаваться чаще, чем нужно.
- В форме с компаратором следите, чтобы