perf(web): release inactive mutation state
This commit is contained in:
26
vehicle-data-platform/apps/web/src/v2/AppV2.cache.test.tsx
Normal file
26
vehicle-data-platform/apps/web/src/v2/AppV2.cache.test.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { QueryClientProvider, useMutation } from '@tanstack/react-query';
|
||||||
|
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import { afterEach, expect, test } from 'vitest';
|
||||||
|
import { createPlatformQueryClient } from './AppV2';
|
||||||
|
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
test('keeps mutation feedback while mounted and releases completed writes after route unmount', async () => {
|
||||||
|
const client = createPlatformQueryClient();
|
||||||
|
const retainedPayload = { rows: Array.from({ length: 500 }, (_, index) => ({ vin: `VIN-${index}`, model: `MODEL-${index}` })) };
|
||||||
|
|
||||||
|
function MutationHarness() {
|
||||||
|
const mutation = useMutation({ mutationFn: async () => retainedPayload });
|
||||||
|
return <div><button type="button" onClick={() => mutation.mutate()}>执行写操作</button>{mutation.data ? <span>已处理 {mutation.data.rows.length} 行</span> : null}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const view = render(<QueryClientProvider client={client}><MutationHarness /></QueryClientProvider>);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: '执行写操作' }));
|
||||||
|
|
||||||
|
expect(await screen.findByText('已处理 500 行')).toBeInTheDocument();
|
||||||
|
expect(client.getMutationCache().getAll()).toHaveLength(1);
|
||||||
|
|
||||||
|
view.unmount();
|
||||||
|
await waitFor(() => expect(client.getMutationCache().getAll()).toHaveLength(0));
|
||||||
|
client.clear();
|
||||||
|
});
|
||||||
@@ -5,17 +5,26 @@ import { AuthGate } from './auth/AuthGate';
|
|||||||
import { RoutePage } from './routing/RouteBoundary';
|
import { RoutePage } from './routing/RouteBoundary';
|
||||||
import { RoutePages } from './routing/routeModules';
|
import { RoutePages } from './routing/routeModules';
|
||||||
import { ROUTER_FUTURE } from './routing/routerConfig';
|
import { ROUTER_FUTURE } from './routing/routerConfig';
|
||||||
|
import { QUERY_MEMORY } from './queryPolicy';
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
export function createPlatformQueryClient() {
|
||||||
defaultOptions: {
|
return new QueryClient({
|
||||||
queries: {
|
defaultOptions: {
|
||||||
staleTime: 15_000,
|
queries: {
|
||||||
gcTime: 5 * 60_000,
|
staleTime: 15_000,
|
||||||
retry: 1,
|
gcTime: 5 * 60_000,
|
||||||
refetchOnWindowFocus: false
|
retry: 1,
|
||||||
|
refetchOnWindowFocus: false
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
gcTime: QUERY_MEMORY.highVolumeGcTime,
|
||||||
|
retry: 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
const queryClient = createPlatformQueryClient();
|
||||||
|
|
||||||
export function AppV2() {
|
export function AppV2() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,6 +2,20 @@
|
|||||||
|
|
||||||
This document records verified risks, the production controls that address them, and the next evidence to collect. It is intentionally operational: a passing build alone is not proof that the browser application is production-ready.
|
This document records verified risks, the production controls that address them, and the next evidence to collect. It is intentionally operational: a passing build alone is not proof that the browser application is production-ready.
|
||||||
|
|
||||||
|
## 2026-07-16: route-scoped mutation memory
|
||||||
|
|
||||||
|
The global QueryClient previously left TanStack Query's mutation garbage-collection policy at its default. Completed writes therefore remained in the Mutation Cache after their page unmounted. Most variables are small, but the vehicle-profile synchronization workflow closes over as many as 500 parsed CSV records and performs a dry run followed by an apply mutation. Repeated administration sessions could retain several completed result graphs and their closures for minutes after leaving the page.
|
||||||
|
|
||||||
|
Completed mutations now use zero inactive retention globally. Pending state, success/error feedback and follow-up invalidation remain available for as long as the owning component is mounted; when the route or panel unmounts, the unused mutation record and any referenced batch payload become immediately eligible for garbage collection. Queries keep their separate reuse policy because read caching improves navigation and is already bounded by data cardinality.
|
||||||
|
|
||||||
|
TanStack Query exposes separate Query and Mutation caches and documents `gcTime` as the lifecycle control for unused cache entries. Chrome's memory guidance likewise identifies lingering JavaScript references as the reason removed application objects remain reachable:
|
||||||
|
|
||||||
|
- <https://tanstack.com/query/v5/docs/reference/QueryClient>
|
||||||
|
- <https://tanstack.com/query/latest/docs/framework/react/guides/mutations>
|
||||||
|
- <https://developer.chrome.com/docs/devtools/memory-problems>
|
||||||
|
|
||||||
|
A component lifecycle test returns a 500-row mutation result, proves the mounted UI can still read it, then unmounts the route owner and proves the Mutation Cache falls from one entry to zero.
|
||||||
|
|
||||||
## 2026-07-16: scope-safe mileage queries
|
## 2026-07-16: scope-safe mileage queries
|
||||||
|
|
||||||
Mileage Statistics previously reused every successful summary and daily-mileage response as placeholder data for the next query key. After applying another vehicle, date range or source strategy, the new controls and column dates appeared immediately while the summary cards and cells could still contain values from the preceding scope. That visual mismatch could make an operator attribute mileage to the wrong period.
|
Mileage Statistics previously reused every successful summary and daily-mileage response as placeholder data for the next query key. After applying another vehicle, date range or source strategy, the new controls and column dates appeared immediately while the summary cards and cells could still contain values from the preceding scope. That visual mismatch could make an operator attribute mileage to the wrong period.
|
||||||
|
|||||||
Reference in New Issue
Block a user