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

Какие есть способы оптимизации производительности 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
Какие есть способы оптимизации производительности React-приложения? | PrepBro