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

Как оптимизировать React приложение, если страница подвисает при действиях пользователя?

2.0 Middle🔥 241 комментариев
#React#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Оптимизация React приложения

Подвисание страницы при действиях пользователя указывает на производительность проблемы. Существует множество стратегий для диагностики и исправления этого.

1. Профилирование с React DevTools Profiler

Первый шаг - найти узкие места:

// React DevTools Profiler показывает:
// - Время рендеринга каждого компонента
// - Количество рендеров
// - Какой компонент пересчитался без причины

// Используй Profiler API для детального анализа
import { Profiler } from "react";

function onRenderCallback(
  id, // компонент
  phase, // mount или update
  actualDuration, // время рендеринга
  baseDuration,
  startTime,
  commitTime
) {
  console.log(`${id} (${phase}) took ${actualDuration}ms`);
}

<Profiler id="MyComponent" onRender={onRenderCallback}>
  <MyComponent />
</Profiler>

2. Мемоизация с React.memo

Предотвращай ненужные рендеры дочерних компонентов:

const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
  console.log("Component rendered");
  return <div>{data}</div>;
});

// Без memo: рендеривается при каждом обновлении родителя
// С memo: рендеривается только если props изменились

// Кастомное сравнение props
const CustomMemo = React.memo(
  MyComponent,
  (prevProps, nextProps) => {
    // true = props равны, пропустить рендер
    // false = пересчитать
    return prevProps.id === nextProps.id;
  }
);

3. useMemo для дорогих вычислений

Кэшируй результаты вычислений:

function MyComponent({ data, filter }) {
  // БЕЗ useMemo - пересчитывается каждый рендер
  // const filteredData = data.filter(item => item.name.includes(filter));
  
  // С useMemo - пересчитывается только если data или filter изменились
  const filteredData = useMemo(
    () => data.filter(item => item.name.includes(filter)),
    [data, filter]
  );

  return <ul>{filteredData.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}

4. useCallback для стабильных функций

Передавай стабильные колбэки дочерним компонентам:

function Parent() {
  // БЕЗ useCallback - новая функция каждый рендер
  // const handleClick = () => { ... };
  
  // С useCallback - та же функция пока зависимости не изменились
  const handleClick = useCallback(() => {
    console.log("Clicked");
  }, []); // пустые зависимости = функция создаётся один раз

  return <Child onClick={handleClick} />; // Child с React.memo не пересчитается
}

5. Код-сплиттинг и ленивая загрузка

Загружай компоненты только когда они нужны:

import { lazy, Suspense } from "react";

const HeavyComponent = lazy(() => import("./HeavyComponent"));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

6. Виртуализация длинных списков

Рендери только видимые элементы:

import { FixedSizeList } from "react-window";

function VirtualList({ items }) {
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={35}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>{items[index]}</div>
      )}
    </FixedSizeList>
  );
}

7. Дебаунсинг и троттлинг

Ограничивай частоту обновлений:

import { useCallback, useRef } from "react";

function useDebounce(callback, delay) {
  const timeoutRef = useRef(null);
  
  return useCallback((...args) => {
    clearTimeout(timeoutRef.current);
    timeoutRef.current = setTimeout(() => callback(...args), delay);
  }, [callback, delay]);
}

function SearchComponent() {
  const [query, setQuery] = useState("");
  
  const debouncedSearch = useDebounce((value) => {
    // API запрос только через 300ms после последнего ввода
    console.log("Searching:", value);
  }, 300);

  return (
    <input
      value={query}
      onChange={(e) => {
        setQuery(e.target.value);
        debouncedSearch(e.target.value);
      }}
    />
  );
}

8. State структура

Избегай больших state объектов:

// ПЛОХО - весь state обновляется => весь компонент рендеривается
const [state, setState] = useState({
  user: {...},
  posts: [...],
  comments: [...]
});

// ХОРОШО - разделяй логику
const [user, setUser] = useState({...});
const [posts, setPosts] = useState([...]);
const [comments, setComments] = useState([...]);

// ИЛИ используй Context для логического разделения
const UserContext = createContext();
const PostContext = createContext();

9. Chrome DevTools Performance Tab

Используй встроенные инструменты:

  1. Открой DevTools -> Performance
  2. Нажми Record
  3. Выполни действие
  4. Посмотри где выскочила жёлтая/красная полоса
  5. Улучшай узкие места

10. Ключевые метрики производительности

// Отслеживай Web Vitals
import { getCLS, getFID, getFCP, getLCP, getTTFB } from "web-vitals";

getLCP(console.log); // Largest Contentful Paint
getFID(console.log); // First Input Delay
getCLS(console.log); // Cumulative Layout Shift

Чеклист оптимизации

  1. Профилируй с React DevTools
  2. Обёртывай дорогие компоненты в React.memo
  3. Кэшируй вычисления useMemo
  4. Стабилизируй функции useCallback
  5. Раздели state логически
  6. Виртуализируй длинные списки
  7. Используй дебаунсинг для input событий
  8. Лениво загружай компоненты
  9. Оптимизируй размер bundle
  10. Отслеживай Web Vitals