← Назад к вопросам
Какие знаешь способы оптимизации отрисовки?
2.0 Middle🔥 191 комментариев
#Оптимизация и производительность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Какие знаешь способы оптимизации отрисовки
Оптимизация отрисовки (rendering performance) — это критичный аспект создания быстрых веб-приложений. Существует множество техник для снижения времени рендера и улучшения FPS (кадров в секунду).
1. React-специфичные оптимизации
useMemo — мемоизация вычисляемых значений
// ❌ Неоптимизировано: фильтр пересчитывается при каждом рендере
function UsersList({ users, filter }) {
const filteredUsers = users.filter(u => u.name.includes(filter));
return (
<div>
{filteredUsers.map(u => <UserCard key={u.id} user={u} />)}
</div>
);
}
// ✅ Оптимизировано: фильтр пересчитывается только если users/filter изменились
import { useMemo } from 'react';
function UsersList({ users, filter }) {
const filteredUsers = useMemo(
() => users.filter(u => u.name.includes(filter)),
[users, filter]
);
return (
<div>
{filteredUsers.map(u => <UserCard key={u.id} user={u} />)}
</div>
);
}
useCallback — мемоизация колбэков
// ❌ Проблема: новая функция создаётся при каждом рендере
function Parent() {
const handleClick = () => console.log('clicked');
return <Child onClick={handleClick} />;
}
// Child перендеряется ненужно, потому что onClick всегда новая
// ✅ Решение: зафиксировать колбэк
import { useCallback } from 'react';
function Parent() {
const handleClick = useCallback(
() => console.log('clicked'),
[] // Пустой массив = функция создаётся один раз
);
return <Child onClick={handleClick} />;
}
React.memo — мемоизация компонентов
// ❌ Компонент перендеряется при каждом рендере родителя
function UserCard({ user }) {
console.log('Rendering', user.name);
return <div>{user.name}</div>;
}
// ✅ Компонент перендеряется только если props изменились
const MemoizedUserCard = React.memo(UserCard);
function UsersList({ users }) {
return users.map(u => <MemoizedUserCard key={u.id} user={u} />);
}
Lazy loading (code splitting)
// ❌ Всё загружается сразу
import HeavyComponent from './HeavyComponent';
// ✅ Компонент загружается по требованию
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
2. DOM и CSS оптимизации
Виртуализация списков (Virtual Scrolling)
// ❌ Проблема: рендерим 10 000 элементов, видим только 20
function HugeList({ items }) {
return (
<div>
{items.map(item => <ListItem key={item.id} item={item} />)}
</div>
);
}
// ✅ Решение: рендерим только видимые элементы
import { FixedSizeList } from 'react-window';
function OptimizedHugeList({ items }) {
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={35}
width="100%"
>
{({ index, style }) => (
<ListItem style={style} item={items[index]} />
)}
</FixedSizeList>
);
}
Debouncing и Throttling для обработчиков
// ❌ Проблема: обработчик вызывается 100+ раз при скролле
function ScrollHandler() {
const handleScroll = () => {
console.log('Scrolling'); // Вызывается очень часто!
};
window.addEventListener('scroll', handleScroll);
}
// ✅ Debounce: вызвать спустя 300мс после последнего события
import { debounce } from 'lodash';
function SearchInput() {
const handleChange = debounce((e) => {
fetchSearchResults(e.target.value);
}, 300);
return <input onChange={handleChange} />;
}
// ✅ Throttle: максимум один раз в 100мс
import { throttle } from 'lodash';
function OptimizedScroll() {
const handleScroll = throttle(() => {
updateLayout();
}, 100);
window.addEventListener('scroll', handleScroll);
}
CSS будет быстрее JavaScript анимации
// ❌ Медленно: JavaScript анимация
function SlideAnimation() {
const [position, setPosition] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setPosition(p => p + 1); // Состояние → рендер → DOM
}, 16);
}, []);
return <div style={{ transform: `translateX(${position}px)` }} />;
}
// ✅ Быстро: CSS анимация
// CSS будет работать на 60 FPS без JS вмешательства
@keyframes slide {
from { transform: translateX(0); }
to { transform: translateX(100px); }
}
.slider {
animation: slide 1s ease-in-out;
will-change: transform; /* Помощь браузеру оптимизировать */
}
will-change свойство
/* Помощь браузеру создать отдельный слой для анимации */
.animated-element {
will-change: transform, opacity;
animation: fadeSlide 1s;
}
/* После анимации убрать will-change (не забыть!) */
.animated-element:not(:hover) {
will-change: auto;
}
3. Optimizing Large Lists
Pagination вместо infinite scroll
// ❌ Infinite scroll: всё хранится в памяти
function InfiniteScrollBad() {
const [items, setItems] = useState([]);
const loadMore = () => {
const newItems = fetchMoreItems();
setItems([...items, ...newItems]); // Растёт бесконечно
};
}
// ✅ Pagination: контролируемое количество
function PaginatedGood() {
const [page, setPage] = useState(1);
const items = useFetch(`/items?page=${page}`); // Загружаем по странам
return (
<div>
{items.map(item => <Item key={item.id} item={item} />)}
<button onClick={() => setPage(p => p + 1)}>Next</button>
</div>
);
}
4. Image Optimization
// ❌ Неоптимизировано
<img src="photo-5000x5000.jpg" width="100" height="100" />
// ✅ Next.js Image (автоматическая оптимизация)
import Image from 'next/image';
<Image
src="/photo.jpg"
width={100}
height={100}
alt="Photo"
loading="lazy" // Lazy load
quality={75} // Сжатие
/>
// ✅ WebP с fallback
<picture>
<source srcSet="/photo.webp" type="image/webp" />
<source srcSet="/photo.jpg" type="image/jpeg" />
<img src="/photo.jpg" alt="Photo" />
</picture>
5. Browser DevTools для анализа
// Performance API для измерения
performance.mark('operation-start');
expensiveOperation();
performance.mark('operation-end');
performance.measure('operation', 'operation-start', 'operation-end');
const measure = performance.getEntriesByName('operation')[0];
console.log(`Operation took ${measure.duration}ms`);
// Или простой способ
const start = performance.now();
expensiveOperation();
const end = performance.now();
console.log(`Took ${end - start}ms`);
Чеклист оптимизации
React:
- Использовать const/useMemo для дорогих вычислений
- Использовать useCallback для коллбэков, передаваемых детям
- Обернуть дорогие компоненты в React.memo
- Code splitting для больших компонентов
- Проверить пропсы в React.memo (deep comparison может быть дорогой)
CSS/DOM:
- CSS анимации вместо JS
- Использовать will-change для анимируемых элементов
- Избегать forced reflows (читать offsetHeight потом писать style)
- Использовать virtual scrolling для больших списков
- Debounce/throttle дорогие обработчики
Изображения и ресурсы:
- Оптимизировать размеры изображений
- Использовать WebP с fallback
- Lazy loading для изображений
- Code splitting для больших бандлов
Измерение:
- Профилировать с DevTools Performance tab
- Использовать Performance API
- Проверять Core Web Vitals (LCP, FID, CLS)
Итог
Оптимизация отрисовки требует комплексного подхода:
- React-уровень: useMemo, useCallback, React.memo, code splitting
- DOM-уровень: virtual scrolling, debouncing, CSS animations
- Ресурсы: image optimization, lazy loading
- Измерение: всегда профилировать перед оптимизацией
Помни: оптимизируй то, что действительно медленно, иначе потратишь время впустую.