Техніки оптимізації продуктивності 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
Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.