01
React Profiler & DevTools
React DevTools Profiler zobrazuje, které komponenty se re-renderují, jak dlouho renderování trvá a proč. Je to první nástroj při výkonnostních problémech.
// Simulace: čas renderu bez/s optimalizací
// React DevTools Profiler API
import { Profiler } from 'react';
function onRenderCallback(
id, // "ProductList"
phase, // "mount" nebo "update"
actualTime, // ms strávených renderem
baseTime, // odhadovaný čas bez memoizace
startTime,
commitTime
) {
console.log(`[${id}] ${phase}: ${actualTime.toFixed(2)}ms`);
// Logovat do monitoring nástroje v produkci
}
<Profiler id="ProductList" onRender={onRenderCallback}>
<ProductList products={products} />
</Profiler>
// Detekce zbytečných re-renderů
// DevTools → Components → "Highlight updates" checkbox
// Každý záblesk = re-render (žlutá = update, modrá = mount)
// Kvíz: Co je "Wasted render" v React DevTools Profileru?
// 01 / 07Memoizace →
02
Memoizace — React.memo, useMemo, useCallback
// React.memo — přeskočí re-render pokud props nezměněny
const ProductCard = React.memo(function ProductCard({ product, onAdd }) {
console.log('ProductCard render:', product.id);
return (
<div>
<h3>{product.nazev}</h3>
<button onClick={() => onAdd(product.id)}>Přidat</button>
</div>
);
}, (prevProps, nextProps) => {
// Custom comparator — true = props stejné, přeskočit render
return prevProps.product.id === nextProps.product.id &&
prevProps.product.cena === nextProps.product.cena;
});
// ❌ Problém: onAdd je nová funkce každý render → memo nefunguje!
function Parent() {
const [count, setCount] = useState(0);
const handleAdd = (id) => addToCart(id); // nová reference!
return <ProductCard onAdd={handleAdd} />;
}
// ✅ useCallback — stabilní reference funkce
function Parent() {
const [count, setCount] = useState(0);
const handleAdd = useCallback((id) => {
addToCart(id);
}, []); // stabilní reference!
return <ProductCard onAdd={handleAdd} />;
}
// useMemo — cache drahého výpočtu
function FilteredList({ products, filter, sort }) {
const processed = useMemo(() => {
console.log('Přepočítávám...'); // jen při změně závislostí
return products
.filter(p => p.kategorie === filter)
.sort((a, b) => a[sort] - b[sort]);
}, [products, filter, sort]);
return <List items={processed} />;
}
// 02 / 07Lazy Loading →
03
Code Splitting & Lazy Loading
// React.lazy + Suspense — dynamický import komponenty
import { lazy, Suspense } from 'react';
// Komponenta se načte jen když je potřeba (lazy chunk)
const AdminPanel = lazy(() => import('./AdminPanel'));
const ProductEditor = lazy(() => import('./ProductEditor'));
const Chart = lazy(() => import('./Chart'));
function App() {
return (
<Suspense fallback={<div>Načítám...</div>}>
<Routes>
<Route path="/admin" element={<AdminPanel />} />
<Route path="/editor" element={<ProductEditor />} />
</Routes>
</Suspense>
);
}
// Next.js dynamic import
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('./HeavyChart'), {
loading: () => <p>Načítám graf...</p>,
ssr: false, // renderovat jen na klientovi
});
// Lazy load images (Intersection Observer)
function LazyImage({ src, alt }) {
const [loaded, setLoaded] = useState(false);
const imgRef = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
setLoaded(true);
observer.disconnect();
}
});
if (imgRef.current) observer.observe(imgRef.current);
return () => observer.disconnect();
}, []);
return (
<img
ref={imgRef}
src={loaded ? src : undefined}
loading="lazy"
alt={alt}
/>
);
}
// Kvíz: Co je "code splitting" a proč ho potřebujeme?
// 03 / 07Virtualizace →
04
Virtualizace seznamů
Renderování 10 000 řádků tabulky = 10 000 DOM elementů = pomalý prohlížeč. Virtualizace renderuje jen viditelné elementy — výkon O(viewport) místo O(n).
// npm install @tanstack/react-virtual
import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';
function VirtualList({ items }: { items: Product[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 60, // odhadovaná výška řádku
overscan: 5, // extra řádků mimo viewport
});
return (
<div
ref={parentRef}
style={{ height: '500px', overflow: 'auto' }}
>
{/* Kontejner s celkovou výškou všech položek */}
<div style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map(virtualRow => (
<div
key={virtualRow.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
}}
>
{/* Renderuje se jen ~10 položek z 100 000 */}
<ProductRow product={items[virtualRow.index]} />
</div>
))}
</div>
</div>
);
}
// Pro jednoduché případy: react-window
import { FixedSizeList } from 'react-window';
<FixedSizeList height={500} itemCount={100000} itemSize={60}>
{({ index, style }) => <Row style={style} data={items[index]} />}
</FixedSizeList>
// 04 / 07Bundle →
05
Bundle analýza & Tree Shaking
# Bundle vizualizace
$ npm install -D rollup-plugin-visualizer # Vite
$ npx vite build --mode analyze
# Nebo: webpack-bundle-analyzer
$ npx webpack-bundle-analyzer dist/stats.json
# Co hledat:
# - Duplicitní závislosti (2x React v bundle)
# - Velké knihovny bez tree shaking (lodash, moment.js)
# - Nevyužité ikony (import { IconA } from 'heroicons' → celá knihovna)
# ✅ Tree shaking — importovat jen co potřebujete
import { format } from 'date-fns'; // jen format (~3kB)
// import moment from 'moment'; // celý moment (~70kB)
import { debounce } from 'lodash-es'; // ES modules lodash
// import _ from 'lodash'; // celý lodash (~70kB)
import { ChevronDown } from 'lucide-react'; // jen jedna ikona
// import * as icons from 'lucide-react'; // všechny ikony
// 05 / 07Caching →
06
Caching strategie
// HTTP Cache-Control
// Statické soubory (content hash v názvu)
Cache-Control: public, max-age=31536000, immutable
// API responses
Cache-Control: public, max-age=300, stale-while-revalidate=60
// Uživatelská data
Cache-Control: private, no-cache
// Service Worker — offline caching
// vite-plugin-pwa nebo next-pwa pro automatické generování
const CACHE = 'v1';
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE).then(cache => cache.addAll([
'/', '/offline.html', '/styles.css'
]))
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(cached => cached || fetch(event.request))
);
});
// React Query cache strategie
const { data } = useQuery({
queryKey: ['products'],
queryFn: fetchProducts,
staleTime: 5 * 60 * 1000, // 5 min = čerstvá data
gcTime: 10 * 60 * 1000, // 10 min = v paměti
refetchOnWindowFocus: 'always',
});
// Kvíz: Co znamená stale-while-revalidate v Cache-Control?
// 06 / 07Cvičení →
07
Cvičení — Performance audit
Proveďte audit React aplikace. Pro každý bod identifikujte problém a navrhněte řešení.
// Najděte výkonnostní problémy:
// 1. Komponenta se re-renderuje příliš často
function ProductList({ products, onDelete, searchTerm }) {
const filtered = products.filter(p =>
p.nazev.toLowerCase().includes(searchTerm)
); // ← Problém?
return (
<div>
{filtered.map(p =>
<ProductCard
key={p.id}
product={p}
onDelete={() => onDelete(p.id)} // ← Problém?
/>
)}
</div>
);
}
// 2. Velký bundle
import moment from 'moment'; // 67kB
// Jak nahradit?
// 3. 50 000 položek v listu
function AllOrders({ orders }) {
return (
<ul>
{orders.map(o => <li key={o.id}>{o.id}</li>)}
</ul>
); // ← Problém + řešení?
}
// Nápověda: useMemo, useCallback, React.memo,
// date-fns místo moment, useVirtualizer
🏅
Performance Engineer
Zvládáte React Profiler, memoizaci, code splitting, virtualizaci a caching.
// 07 / 07Taháček →
08
// Taháček
// Memoizace
const MemoComp = React.memo(Component, compareFn);
const value = useMemo(() => expensiveCalc(a,b), [a,b]);
const fn = useCallback((x) => doSomething(x), [dep]);
// Code splitting
const Lazy = lazy(() => import('./Lazy'));
<Suspense fallback={<Spinner/>}><Lazy/></Suspense>
// Next.js dynamic
const Dyn = dynamic(() => import('./Dyn'), { ssr: false });
// Virtualizace
const virt = useVirtualizer({ count: N, getScrollElement, estimateSize: () => 60 });
virt.getVirtualItems() // jen viditelné řádky
// Cache-Control
"public, max-age=31536000, immutable" // statické soubory
"public, max-age=300, stale-while-revalidate=60" // API
// Lekce 11 dokončenaL12: Architektura →