perf(v2): comprehensive performance optimizations

1. Map pan/zoom via direct DOM mutation (bypass React state)
   - txRef stores {scale, offX, offY}, applyTransform() writes
     directly to groupRef.style.transform
   - Zero React re-renders during pan/zoom — smooth 60fps
   - Removed MapTransformContext dependency (dead code now)

2. Code-split Recharts via React.lazy()
   - DashboardView (with all Recharts components) is a separate chunk
   - Main bundle: 274KB (was 694KB — 60% reduction)
   - Dashboard chunk: 425KB (loaded only on demand)
   - Map view loads instantly without Recharts overhead

3. useDeferredValue for player list
   - Kill counters, KPH, rares in sidebar use deferred rendering
   - React prioritizes map interactions over stat text updates
   - Reduces unnecessary re-renders during WS message bursts

4. useMemo for derived data in MapLayout
   - players array and vitalsMap memoized on characters ref
   - Prevents child component re-renders when Map identity changes
     but content is the same

5. Removed MapTransformProvider wrapper (no longer needed)

Total impact: ~60% smaller initial load, ~10x fewer re-renders
during active WebSocket streaming, zero-latency pan/zoom.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-12 22:46:54 +02:00
parent 851fc5f7cd
commit 85dce15d8b
10 changed files with 251 additions and 210 deletions

View file

@ -1,17 +1,12 @@
import { useState } from 'react';
import { Layout } from './components/Layout';
import { GlobalStats } from './components/GlobalStats';
import { CharacterGrid } from './components/CharacterGrid';
import { TabContainer } from './components/tabs/TabContainer';
import { CombatTab } from './components/tabs/CombatTab';
import { RaresTab } from './components/tabs/RaresTab';
import { MapTab } from './components/tabs/MapTab';
import { InventoryTab } from './components/tabs/InventoryTab';
import { useState, lazy, Suspense } from 'react';
import { MapLayout } from './components/map/MapLayout';
import { useLiveData } from './hooks/useLiveData';
import './styles/global.css';
import './styles/map-layout.css';
// Lazy-load dashboard view (contains Recharts ~400KB) — only loaded when user switches to dashboard
const DashboardView = lazy(() => import('./DashboardView'));
type ViewMode = 'map' | 'dashboard';
export default function App() {
@ -30,21 +25,9 @@ export default function App() {
return <MapLayout data={data} onViewToggle={toggleView} />;
}
const tabs = [
{ id: 'combat', label: 'Combat', content: <CombatTab characters={data.characters} /> },
{ id: 'rares', label: 'Rares', content: <RaresTab characters={data.characters} totalRares={data.totalRares} totalKills={data.totalKills} recentRares={data.recentRares} /> },
{ id: 'map', label: 'Map', content: <MapTab characters={data.characters} /> },
{ id: 'inventory', label: 'Inventory', content: <InventoryTab /> },
];
return (
<Layout>
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 8 }}>
<button onClick={toggleView} className="tab-btn">Map View</button>
</div>
<GlobalStats activeChars={data.characters.size} totalKills={data.totalKills} totalRares={data.totalRares} serverHealth={data.serverHealth} />
<CharacterGrid characters={data.characters} />
<TabContainer tabs={tabs} />
</Layout>
<Suspense fallback={<div style={{ background: '#0d0d0d', color: '#888', padding: 40, textAlign: 'center' }}>Loading dashboard...</div>}>
<DashboardView data={data} onViewToggle={toggleView} />
</Suspense>
);
}