← Назад к вопросам

Какие знаешь способы оптимизации отрисовки?

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
  • Измерение: всегда профилировать перед оптимизацией

Помни: оптимизируй то, что действительно медленно, иначе потратишь время впустую.

Какие знаешь способы оптимизации отрисовки? | PrepBro