perf(web): свод 2 — refine по условиям данных, измерение высоты вне загрузки, окно по поколению
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d74f5286f9
commit
fbc3601673
@ -382,7 +382,7 @@ export default function Svod2Page() {
|
||||
},
|
||||
muiTableContainerProps: { sx: SVOD2_TABLE_CONTAINER_SX, onScroll },
|
||||
muiTableBodyRowProps: ({ row }) =>
|
||||
isPlaceholderRow(row.original) ? { className: 'svod2-placeholder', 'data-svod2-placeholder': '' } : {},
|
||||
isPlaceholderRow(row.original) ? { className: 'svod2-placeholder', 'data-svod2-placeholder': '', style: { height: rowHeight } } : {},
|
||||
muiTableHeadProps: {
|
||||
sx: {
|
||||
'& tr:not(:last-of-type) > th > .MuiCollapse-root': {
|
||||
@ -400,9 +400,14 @@ export default function Svod2Page() {
|
||||
});
|
||||
tableRef.current = table;
|
||||
// Единая высота строки для окна страниц: реальная высота первой строки с данными (по умолчанию ROW_HEIGHT).
|
||||
// Не измеряем во время загрузки (MRT рисует скелетон другой высоты); берём максимум по строкам с данными.
|
||||
useLayoutEffect(() => {
|
||||
const element = tableRef.current?.refs.tableContainerRef.current?.querySelector('tbody tr:not([data-svod2-placeholder])');
|
||||
const measured = element ? Math.round(element.offsetHeight) : 0;
|
||||
if (loading) return;
|
||||
const elements = tableRef.current?.refs.tableContainerRef.current?.querySelectorAll(
|
||||
'tbody tr[data-index]:not([data-svod2-placeholder])',
|
||||
);
|
||||
let measured = 0;
|
||||
for (const element of elements ?? []) measured = Math.max(measured, Math.round(element.offsetHeight));
|
||||
if (measured > 0 && measured !== rowHeight) setRowHeight(measured);
|
||||
});
|
||||
let content = <MaterialReactTable table={table} />;
|
||||
|
||||
@ -104,8 +104,9 @@ export function shouldShowFound({ hasCurrentData, loading, facets, error = '' })
|
||||
|
||||
// Нужно ли уточнение (refine) при смене поиска/колоночных фильтров: на загруженных данных
|
||||
// либо пока для текущего queryKey ещё летит первая загрузка (иначе ответы станут «текущими» по старым условиям).
|
||||
export function shouldRefine({ hasCurrentData, loading, pendingQueryKey, queryKey, prevRefineKey, refineKey }) {
|
||||
if (prevRefineKey === refineKey) return false;
|
||||
// dataRefineKey — условия, на которых построены загруженные (или летящие) данные вкладки; нет их — нет и refine.
|
||||
export function shouldRefine({ hasCurrentData, loading, pendingQueryKey, queryKey, dataRefineKey, refineKey }) {
|
||||
if (dataRefineKey === undefined || dataRefineKey === null || dataRefineKey === refineKey) return false;
|
||||
if (hasCurrentData) return true;
|
||||
return Boolean(loading) && pendingQueryKey === queryKey;
|
||||
}
|
||||
|
||||
@ -285,13 +285,25 @@ test('«Найдено» не показывается рядом с ошибк
|
||||
assert.equal(shouldShowFound({ hasCurrentData: true, loading: false, error: 'x', facets: { total: 5, search_total: 1 } }), false);
|
||||
});
|
||||
test('shouldRefine: на загруженных данных при смене условий', () => {
|
||||
const base = { hasCurrentData: true, loading: false, pendingQueryKey: 'q', queryKey: 'q', prevRefineKey: 'a', refineKey: 'b' };
|
||||
const base = { hasCurrentData: true, loading: false, pendingQueryKey: 'q', queryKey: 'q', dataRefineKey: 'a', refineKey: 'b' };
|
||||
assert.equal(shouldRefine(base), true);
|
||||
assert.equal(shouldRefine({ ...base, refineKey: 'a' }), false);
|
||||
});
|
||||
test('shouldRefine: поиск изменён во время первой загрузки (T12.7)', () => {
|
||||
const base = { hasCurrentData: false, loading: true, pendingQueryKey: 'q', queryKey: 'q', prevRefineKey: 'a', refineKey: 'b' };
|
||||
const base = { hasCurrentData: false, loading: true, pendingQueryKey: 'q', queryKey: 'q', dataRefineKey: 'a', refineKey: 'b' };
|
||||
assert.equal(shouldRefine(base), true);
|
||||
assert.equal(shouldRefine({ ...base, pendingQueryKey: 'old' }), false);
|
||||
assert.equal(shouldRefine({ ...base, loading: false }), false);
|
||||
});
|
||||
test('shouldRefine: ключ A -> B -> A при изменённых фильтрах', () => {
|
||||
// данные ключа A построены на фильтре 'a'; на B фильтры сброшены (''), возврат на A — данные устарели
|
||||
const onA = { hasCurrentData: true, loading: false, pendingQueryKey: 'A', queryKey: 'A', dataRefineKey: 'a', refineKey: '' };
|
||||
assert.equal(shouldRefine(onA), true);
|
||||
const onB = { ...onA, hasCurrentData: false, queryKey: 'B' };
|
||||
assert.equal(shouldRefine(onB), false);
|
||||
});
|
||||
test('shouldRefine: первое монтаж и смена вкладки без расхождения не запускают refine', () => {
|
||||
const base = { hasCurrentData: false, loading: false, pendingQueryKey: '', queryKey: 'q', refineKey: 'a' };
|
||||
assert.equal(shouldRefine({ ...base, dataRefineKey: undefined }), false);
|
||||
assert.equal(shouldRefine({ ...base, hasCurrentData: true, dataRefineKey: 'a' }), false);
|
||||
});
|
||||
|
||||
@ -34,6 +34,8 @@ const getRawPage = (scrollTop, rowHeight) => {
|
||||
return Number.isFinite(page) && page > 0 ? page : 0;
|
||||
};
|
||||
|
||||
const makeRefineKey = (search, columnFilters) => JSON.stringify([search, buildSvod2QueryParams({ columnFilters })]);
|
||||
|
||||
const isCanceled = (error, ctx) => axios.isCancel(error) || error?.code === 'ERR_CANCELED' || ctx.controller.signal.aborted;
|
||||
|
||||
export function useSvod2Pages({ sheet, year, orgIds, queryKey, search, columnFilters, rowHeight, getScrollTop }) {
|
||||
@ -101,6 +103,7 @@ export function useSvod2Pages({ sheet, year, orgIds, queryKey, search, columnFil
|
||||
controller: new AbortController(),
|
||||
inflight: new Set(),
|
||||
ready: false,
|
||||
refineKey: makeRefineKey(current.search, current.columnFilters),
|
||||
};
|
||||
ctxRef.current[sheetKey] = ctx;
|
||||
const candidate = getRawPage(current.getScrollTop(), current.rowHeight);
|
||||
@ -194,7 +197,11 @@ export function useSvod2Pages({ sheet, year, orgIds, queryKey, search, columnFil
|
||||
if (!ctx || !ctx.ready || ctx.controller.signal.aborted) return;
|
||||
if (sheetState.generation !== ctx.generation || sheetState.loadedQueryKey !== current.queryKey) return;
|
||||
const pages = getWindowFor(sheetState.total);
|
||||
for (const pageIndex of getMissingPages(pages, sheetState.pages, ctx.inflight)) {
|
||||
for (const pageIndex of getMissingPages(
|
||||
pages,
|
||||
sheetState.dataGeneration === ctx.generation ? sheetState.pages : new Map(),
|
||||
ctx.inflight,
|
||||
)) {
|
||||
fetchPage(ctx, pageIndex)
|
||||
.then((result) => {
|
||||
if (ctx.controller.signal.aborted) return;
|
||||
@ -226,28 +233,28 @@ export function useSvod2Pages({ sheet, year, orgIds, queryKey, search, columnFil
|
||||
// Смена поиска или колоночных фильтров на загруженных данных — уточнение (refine) теми же запросами.
|
||||
const sheetState = state[sheet];
|
||||
const hasCurrentData = sheetState.loadedQueryKey === queryKey;
|
||||
const refineKey = JSON.stringify([search, buildSvod2QueryParams({ columnFilters })]);
|
||||
const previousRef = useRef({ sheet, refineKey });
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: реагируем только на смену поиска/фильтров и вкладки
|
||||
const refineKey = makeRefineKey(search, columnFilters);
|
||||
// Условия, на которых построены данные вкладки (или летящего поколения), хранятся в ctx: сравнение с ними, а не с прошлым рендером.
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: start и поля sheetState читаются из актуального рендера
|
||||
useEffect(() => {
|
||||
const previous = previousRef.current;
|
||||
previousRef.current = { sheet, refineKey };
|
||||
if (previous.sheet !== sheet) {
|
||||
syncWindow();
|
||||
return;
|
||||
}
|
||||
if (
|
||||
shouldRefine({
|
||||
hasCurrentData,
|
||||
loading: sheetState.loading,
|
||||
pendingQueryKey: sheetState.pendingQueryKey,
|
||||
queryKey,
|
||||
prevRefineKey: previous.refineKey,
|
||||
dataRefineKey: ctxRef.current[sheet]?.refineKey,
|
||||
refineKey,
|
||||
})
|
||||
)
|
||||
start('refine');
|
||||
}, [sheet, refineKey]);
|
||||
}, [sheet, refineKey, hasCurrentData, queryKey, sheetState.loading, sheetState.pendingQueryKey]);
|
||||
|
||||
// Смена вкладки или высоты строки — пересчёт окна страниц.
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: syncWindow читает актуальные props из ref
|
||||
useEffect(() => {
|
||||
syncWindow();
|
||||
}, [sheet, rowHeight]);
|
||||
|
||||
useEffect(() => {
|
||||
mountedRef.current = true;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user