Skip to main content
Практика завдань

Техніки оптимізації продуктивності React

Оптимізація продуктивності в React

React за замовчуванням швидкий, але великі додатки можуть страждати від необхідних повторних рендерів, важких обчислень та великих розмірів бандлів. Ось ключові техніки для оптимізації продуктивності React.


1. React.memo — Запобігання непотрібним повторним рендерингам

tsx
// Повторно рендериться ТІЛЬКИ коли змінюються props (поверхневе порівняння) const ExpensiveList = React.memo(function ExpensiveList({ items }: { items: Item[] }) { return ( <ul> {items.map(item => <li key={item.id}>{item.name}</li>)} </ul> ); });

2. useMemo — Кешування важких обчислень

tsx
function Dashboard({ transactions }: { transactions: Transaction[] }) { // Перераховується тільки коли змінюються транзакції const stats = useMemo(() => { return { total: transactions.reduce((sum, t) => sum + t.amount, 0), average: transactions.reduce((sum, t) => sum + t.amount, 0) / transactions.length, max: Math.max(...transactions.map(t => t.amount)), }; }, [transactions]); return <StatsCard stats={stats} />; }

3. useCallback — Стабільні посилання на функції

tsx
function Parent() { const [count, setCount] = useState(0); // Без useCallback: нова функція при кожному рендері → Дитина повторно рендериться // З useCallback: те саме посилання на функцію → Дитина пропускає повторний рендер const handleClick = useCallback(() => { console.log("clicked"); }, []); // Стабільне посилання return ( <div> <span>{count}</span> <button onClick={() => setCount(c => c + 1)}>+</button> <MemoizedChild onClick={handleClick} /> </div> ); } const MemoizedChild = React.memo(function Child({ onClick }: { onClick: () => void }) { console.log("Child rendered"); return <button onClick={onClick}>Click me</button>; });

4. Розділення коду з React.lazy

tsx
import { lazy, Suspense } from "react"; // Компонент завантажується тільки коли це потрібно const HeavyChart = lazy(() => import("./HeavyChart")); const AdminPanel = lazy(() => import("./AdminPanel")); function App() { return ( <Suspense fallback={<Loading />}> <Routes> <Route path="/dashboard" element={<HeavyChart />} /> <Route path="/admin" element={<AdminPanel />} /> </Routes> </Suspense> ); }

5. Віртуалізація для великих списків

tsx
import { useVirtualizer } from "@tanstack/react-virtual"; function VirtualList({ items }: { items: Item[] }) { const parentRef = useRef<HTMLDivElement>(null); const virtualizer = useVirtualizer({ count: items.length, // 10,000+ елементів getScrollElement: () => parentRef.current, estimateSize: () => 50, // Висота рядка }); return ( <div ref={parentRef} style={{ height: "400px", overflow: "auto" }}> <div style={{ height: virtualizer.getTotalSize() }}> {virtualizer.getVirtualItems().map(virtualRow => ( <div key={virtualRow.key} style={{ position: "absolute", top: virtualRow.start, height: virtualRow.size, }} > {items[virtualRow.index].name} </div> ))} </div> </div> ); }

6. Уникати інлайн-об'єктів і функцій

tsx
// ❌ Новий об'єкт при кожному рендері → дитина повторно рендериться <Child style={{ color: "red" }} /> <Child onClick={() => console.log("click")} /> // ✅ Стабільні посилання const style = useMemo(() => ({ color: "red" }), []); const handleClick = useCallback(() => console.log("click"), []); <Child style={style} /> <Child onClick={handleClick} />

7. Співрозташування стану

Тримайте стан якомога ближче до того, де він використовується:

tsx
// ❌ Стан занадто високий — весь додаток повторно рендериться при наведенні function App() { const [hoveredId, setHoveredId] = useState<string | null>(null); return <List items={items} hoveredId={hoveredId} onHover={setHoveredId} />; } // ✅ Стан співрозташований — тільки ListItem повторно рендериться function ListItem({ item }: { item: Item }) { const [isHovered, setIsHovered] = useState(false); return ( <div onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} className={isHovered ? "highlighted" : ""} > {item.name} </div> ); }

Швидка довідка

ПроблемаРішення
Дитина повторно рендериться, коли батько повторно рендеритьсяReact.memo
Важке обчислення виконується при кожному рендеріuseMemo
Пропс зворотного виклику викликає повторний рендерuseCallback + React.memo
Великий початковий бандлРозділення коду (React.lazy)
Відображення 10,000+ елементівВіртуалізація
Зміни стану викликають широкомасштабні повторні рендериСпіврозташування стану
Важкі не термінові оновленняuseTransition / useDeferredValue

Важливо:

Не оптимізуйте передчасно — React за замовчуванням швидкий. Спочатку профілюйте за допомогою профайлера React DevTools, визначте фактичні вузькі місця, а потім застосуйте цілеспрямовані оптимізації. Найбільші виграші зазвичай приходять від співрозташування стану, розділення коду та віртуалізації, а не від memo/useMemo всюди.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?
Практика завдань