synapse-storage
Version:
Набор инструментов для управления состоянием и апи-запросами
70 lines (63 loc) • 3.21 kB
JavaScript
import { useCallback, useRef, useSyncExternalStore } from "react";
import { logError } from "../../_utils/error-handling.util.js";
/**
* Хук для подписки на изменения в хранилище.
* Использует useSyncExternalStore для корректной работы в Concurrent Mode.
*
* Принимает IStorageBase (общий интерфейс для sync и async хранилищ),
* так как подписки одинаковы для всех типов.
*
* @template S - Тип состояния хранилища
* @template R - Тип возвращаемого значения
* @param storage - Экземпляр хранилища или null (до инициализации)
* @param selector - Функция-селектор для выбора данных
* @param options - Опции: `equals` для мемоизации снапшота (см. {@link UseStorageSubscribeOptions})
* @returns Значение из хранилища
*/ const useStorageSubscribe = (storage, selector, options)=>{
// Храним selector в ref, чтобы не пересоздавать subscribe/getSnapshot при изменении ссылки
const selectorRef = useRef(selector);
selectorRef.current = selector;
const equalsRef = useRef(options?.equals);
equalsRef.current = options?.equals;
// Кеш последнего снапшота — нужен и для мемоизации по equals, и чтобы вернуть
// стабильную ссылку (иначе useSyncExternalStore при селекторе-фабрике объекта
// зациклится на бесконечном ререндере).
const cachedRef = useRef(undefined);
const hasCacheRef = useRef(false);
const subscribe = useCallback((onStoreChange)=>{
if (!storage) return ()=>{};
// Подписка на все изменения storage + на смену статуса (для момента инициализации)
const unsubData = storage.subscribeToAll(()=>onStoreChange());
const unsubStatus = storage.onStatusChange(()=>onStoreChange());
return ()=>{
unsubData();
unsubStatus();
};
}, [
storage
]);
const getSnapshot = useCallback(()=>{
if (!storage) return undefined;
try {
const state = storage.getStateSync();
const value = selectorRef.current(state);
const equals = equalsRef.current;
if (equals && hasCacheRef.current && equals(cachedRef.current, value)) {
return cachedRef.current;
}
cachedRef.current = value;
hasCacheRef.current = true;
return value;
} catch (error) {
if (process.env.NODE_ENV !== 'production') {
logError('useStorageSubscribe: selector error', error, null, 'warn');
}
return undefined;
}
}, [
storage
]);
return useSyncExternalStore(subscribe, getSnapshot);
};
export { useStorageSubscribe };
//# sourceMappingURL=useStorageSubscribe.js.map