synapse-storage
Version:
Набор инструментов для управления состоянием и апи-запросами
75 lines (70 loc) • 3.37 kB
JavaScript
import { useCallback, useRef, useSyncExternalStore } from "react";
function useSelector(selector, options) {
const equalsRef = useRef(options?.equals);
// Кеш для мемоизации результата getSnapshot (предотвращает лишние ререндеры)
const cachedRef = useRef(undefined);
const subscribe = useCallback((onStoreChange)=>{
return selector.subscribe({
notify: ()=>{
onStoreChange();
}
});
}, [
selector
]);
const getSnapshot = useCallback(()=>{
const value = selector.selectSync();
// Если есть пользовательская функция сравнения — мемоизируем
const equals = equalsRef.current;
if (equals && cachedRef.current !== undefined && equals(cachedRef.current, value)) {
return cachedRef.current;
}
cachedRef.current = value;
return value;
}, [
selector
]);
// getServerSnapshot === getSnapshot: на сервере `selectSync()` синхронно читает
// засеянный (hydrate) стор. Без серверного снапшота useSyncExternalStore на сервере
// падает и откатывается на client-only рендер (контент не попадает в SSR-HTML).
const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
// Подписка на статус готовности storage (используется только при withLoading)
const subscribeToStatus = useCallback((onStoreChange)=>{
return selector.onSourceStatusChange(()=>{
onStoreChange();
});
}, [
selector
]);
const getStatusSnapshot = useCallback(()=>{
return !selector.isSourceReady();
}, [
selector
]);
const isLoading = useSyncExternalStore(subscribeToStatus, getStatusSnapshot, getStatusSnapshot);
if (options?.withLoading) {
return {
data: value,
isLoading
};
}
return value;
}
/**
* Изоляция ре-рендеров для keyed-map стора (паттерн «equals»). Подписка идёт на
* ВЕСЬ map (`selector`), но `equals` сравнивает только срез по `key` → компонент
* ре-рендерится лишь когда меняется его `map[key]` по ссылке. Работает, т.к.
* иммутабельные мутации по одному ключу не трогают ссылку чужих срезов.
* Гранулярность живёт в месте вызова, а селекторы остаются плоскими (без фабрик
* с `Map<key, selector>`).
*
* `fallback` ОБЯЗАН быть стабильной ссылкой (module-level константа), иначе
* `?? fallback` будет давать новый объект каждый тик и рвать нижестоящие `useMemo`.
*/ function useKeyedSliceSelector(selector, key, fallback) {
const map = useSelector(selector, {
equals: (a, b)=>a[key] === b[key]
});
return map[key] ?? fallback;
}
export { useKeyedSliceSelector, useSelector };
//# sourceMappingURL=useSelector.js.map