← Назад к вопросам
Какие есть способы оптимизации производительности React-приложения?
2.0 Middle🔥 181 комментариев
#React#Оптимизация и производительность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Какие есть способы оптимизации производительности React-приложения?
Оптимизация производительности React — критична для хорошего пользовательского опыта. Рассмотрим основные стратегии и инструменты.
1. Мемоизация компонентов и функций
React.memo — предотвращает перерендеринг при одинаковых props:
// ❌ Без мемоизации
function ProductCard({ product, onBuy }) {
console.log("Rendring ProductCard");
return (
<div>
<h3>{product.name}</h3>
<button onClick={onBuy}>Купить</button>
</div>
);
}
// ✅ С мемоизацией
const ProductCard = React.memo(function ProductCard({ product, onBuy }) {
console.log("Rendering ProductCard");
return (
<div>
<h3>{product.name}</h3>
<button onClick={onBuy}>Купить</button>
</div>
);
});
// Пользовательское сравнение props
const MemoizedCard = React.memo(
ProductCard,
(prevProps, nextProps) => {
// Вернуть true, если пропсы равны (не рендерим)
return prevProps.product.id === nextProps.product.id;
}
);
useMemo — мемоизирует дорогостоящие вычисления:
function ProductList({ products, sortBy }) {
// ❌ Пересчитывается при каждом рендере
// const sortedProducts = products.sort((a, b) => a[sortBy] - b[sortBy]);
// ✅ Пересчитывается только при изменении зависимостей
const sortedProducts = useMemo(
() => products.sort((a, b) => a[sortBy] - b[sortBy]),
[products, sortBy] // зависимости
);
return (
<ul>
{sortedProducts.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
);
}
useCallback — мемоизирует функции для избежания пересоздания:
function Parent() {
const [count, setCount] = useState(0);
// ❌ Новая функция при каждом рендере
// const handleClick = () => setCount(c => c + 1);
// ✅ Функция переиспользуется, если зависимостей нет
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []); // пустой массив — функция никогда не меняется
return (
<>
<Child onClick={handleClick} />
<p>Count: {count}</p>
</>
);
}
const Child = React.memo(({ onClick }) => (
<button onClick={onClick}>Increment</button>
));
2. Код-сплиттинг и ленивая загрузка
dynamic import с React.lazy:
// analytics.js — большой модуль, загружается отдельно
const AnalyticsComponent = React.lazy(() => import("./Analytics"));
function App() {
return (
<Suspense fallback={<div>Загрузка...</div>}>
<AnalyticsComponent />
</Suspense>
);
}
Route-based code splitting:
import { lazy, Suspense } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
const Home = lazy(() => import("./pages/Home"));
const About = lazy(() => import("./pages/About"));
const Admin = lazy(() => import("./pages/Admin"));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/admin" element={<Admin />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
3. Оптимизация списков
Key и виртуализация больших списков:
import { FixedSizeList } from "react-window";
// ❌ Плохо — рендерит 10000 элементов
function BadList({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li> // ✅ key обязателен!
))}
</ul>
);
}
// ✅ Хорошо — рендерит только видимые элементы
function GoodList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>{items[index].name}</div>
);
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={35}
width="100%"
>
{Row}
</FixedSizeList>
);
}
Избегаем анонимных функций как keys:
// ❌ Никогда не используй индекс как key для изменяемых списков
list.map((item, index) => <Item key={index} item={item} />)
// ✅ Используй уникальный id
list.map(item => <Item key={item.id} item={item} />)
4. Оптимизация render-функций
Избегаем создания объектов в рендере:
function Component({ data }) {
// ❌ Новый объект при каждом рендере
// return <Child style={{ color: "red", fontSize: "16px" }} />
// ✅ Константа вне функции или в useMemo
const style = useMemo(() => ({ color: "red", fontSize: "16px" }), []);
return <Child style={style} />;
}
// ✅ Ещё лучше — константа вне компонента
const BUTTON_STYLE = { color: "red", fontSize: "16px" };
function Component() {
return <Child style={BUTTON_STYLE} />;
}
5. Инструменты профилирования
React DevTools Profiler:
// Окрыть DevTools → Profiler вкладка
// 1. Нажать "Record" кнопку
// 2. Выполнить действие в приложении
// 3. Посмотреть, какие компоненты перерендерились
// Поискать: желтые / красные компоненты
// Нажать на них, чтобы увидеть "Render duration"
Lighthouse для метрик:
DevTools → Lighthouse
- Запустить аудит
- Посмотреть: Performance, LCP, FID, CLS
6. Управление состоянием
Разделение состояния по смыслу:
// ❌ Монолитное состояние
const [appState, setAppState] = useState({
user: null,
theme: "light",
notifications: [],
isLoading: false
});
// ✅ Разделённое состояние
const [user, setUser] = useState(null);
const [theme, setTheme] = useState("light");
const [notifications, setNotifications] = useState([]);
const [isLoading, setIsLoading] = useState(false);
// Компоненты, зависящие от theme, не перерендерят при изменении user
Используй контекст с разделением:
// ✅ Разные контексты для разных забот
const UserContext = createContext();
const ThemeContext = createContext();
const NotificationsContext = createContext();
// Компонент, зависящий только от theme, не перерендерит при user изменении
7. Производительность загрузки
Image optimization:
// ❌ Плохо
<img src="/large-image.jpg" />
// ✅ Хорошо (Next.js Image компонент)
import Image from "next/image";
<Image
src="/image.jpg"
alt="Description"
width={800}
height={600}
priority // для критичных изображений
placeholder="blur" // blur во время загрузки
/>
8. Дебаунсинг и троттлинг
// Debounce — вызывается через X ms после последнего события
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
// Используем при поиске
function SearchUsers({ onSearch }) {
const [input, setInput] = useState("");
const debouncedInput = useDebounce(input, 500);
useEffect(() => {
if (debouncedInput) {
onSearch(debouncedInput);
}
}, [debouncedInput]);
return <input value={input} onChange={e => setInput(e.target.value)} />;
}
Чеклист оптимизации
- ✅ React.memo для дорогих компонентов
- ✅ useMemo для дорогих вычислений
- ✅ useCallback для функций, передаваемых детям
- ✅ Code splitting по роутам
- ✅ Виртуализация больших списков
- ✅ Правильные keys в списках
- ✅ Отделение контекста по смыслу
- ✅ Профилирование в DevTools
- ✅ Проверка метрик в Lighthouse