Назад
Назад к заметкам
· 4 мин чтения

Рефакторинг кода в React 2025: практический подход

Как улучшить React-приложение в 2025 году: профилирование, хуки, memo и тесты.

reactrefactoringperformancebest-practices

Если ваш список элементов начал лагать, а консоль полна предупреждений о лишних ререндеров, значит пришло время рефакторинга. Важно сразу понять, где находятся узкие места и как их устранить, не нарушив существующий функционал.


1. Поймать “тормоза”: где начинается оптимизация

Первый шаг выяснить, что именно тормозит приложение. Самый простой способ включить DevTools → Profiler и посмотреть, какие компоненты перерисовываются чаще всего. Часто виноваты неоптимальные props или слишком “толстые” функции внутри компонента. Здесь собираем метрики, формируем план и продолжаем.

Tip: Если видите, что один и тот же компонент рендерится 20-30 раз за секунду без видимых изменений это сигнал к действию.

Что искать в профайлере

  • Commit count количество render-ов.
  • Render duration время, затраченное на каждый рендер.
  • Component name имя наиболее “тяжелого” компонента.

Эти метрики позволяют построить небольшую таблицу проблемных участков.

КомпонентКол-во ререндеровСреднее время (ms)Причина
UserList4512Передается новый массив users каждый раз
Chart3025Вычисления без useMemo
Sidebar208Передается объект styles как литерал

Дальше разберем каждый пункт, показывая, как рефакторинг превращает громоздкий код в чистую архитектуру.


2. Разделить логику на модульные хуки

Слишком много бизнес-логики в JSX-файлах частый симптом “грязного” кода. Перенесите ее в кастомные хуки. Это упрощает тестирование и делает компоненты более читабельными.

// useUserFilter.ts
import { useMemo } from "react";

export function useUserFilter(users, query) {
  return useMemo(() => {
    if (!query) return users;
    return users.filter((u) =>
      u.name.toLowerCase().includes(query.toLowerCase())
    );
  }, [users, query]);
}
// UserList.tsx
import { useUserFilter } from "./useUserFilter";

export default function UserList({ users, query }) {
  const filtered = useUserFilter(users, query);
  return (
    <ul>
      {filtered.map((u) => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  );
}

Теперь UserList отвечает лишь за рендер, а фильтрация живет в отдельном хуке.


3. React.memo и useCallback для снижения лишних ререндеров

Когда компонент получает функции через props, каждый новый рендер создает новую функцию-объект, заставляя дочерний компонент перерисовываться. Оберните такой компонент в React.memo и стабилизируйте коллбэки с помощью useCallback.

// Item.tsx
import React from "react";

function Item({ data, onSelect }) {
  console.log("render Item", data.id);
  return <div onClick={() => onSelect(data.id)}>{data.title}</div>;
}

export default React.memo(Item);
// ItemList.tsx
import React, { useCallback } from "react";
import Item from "./Item";

export default function ItemList({ items, onSelect }) {
  const handleSelect = useCallback((id) => onSelect(id), [onSelect]);
  return (
    <>
      {items.map((item) => (
        <Item key={item.id} data={item} onSelect={handleSelect} />
      ))}
    </>
  );
}

React.memo запоминает прошлый набор props, а useCallback гарантирует стабильность функции. Подробнее о снижении ререндеров статья Оптимизация рендеринга в React.


4. Структурирование компонентов: вынесение тяжелых функций в утилиты

Если внутри компонента находится сложный алгоритм (например, подготовка данных для графика), лучше вынести его в отдельный модуль. Это упрощает unit-тесты и делает зависимость от React более явной.

// chart-utils.ts
export function normalizeData(raw) {
  const max = Math.max(...raw.map((r) => r.value));
  return raw.map((r) => ({ ...r, percent: (r.value / max) * 100 }));
}
// Chart.tsx
import { normalizeData } from "./chart-utils";
import { useMemo } from "react";

export default function Chart({ data }) {
  const normalized = useMemo(() => normalizeData(data), [data]);
  // рендер графика …
  return <svg>{/* … */}</svg>;
}

Отделив бизнес-логику, получаем чистый React-компонент, который легко покрыть тестами.


5. Тестировать изменения без регрессий

После рефакторинга сразу проверяйте, что поведение не сломалось. Для React 2025 хорошей практикой считается комбинация Jest, React Testing Library и Playwright для end-to-end-проверок.

import { render, screen } from "@testing-library/react";
import UserList from "./UserList";

test("отображает отфильтрованных пользователей", () => {
  const users = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
  ];
  render(<UserList users={users} query="al" />);
  expect(screen.getByText("Alice")).toBeInTheDocument();
  expect(screen.queryByText("Bob")).not.toBeInTheDocument();
});

Регулярный code-review помогает обнаружить анти-паттерны, о которых рассказывается в нашем блоге.


FAQ / Чеклист

  • Не мутировать props напрямую используйте spread-оператор или утилиты.
  • Не оставлять анонимные функции в JSX без useCallback они приводят к лишним ререндерям.
  • Не забывать ставить React.memo только для действительно дорогих компонентов.
  • Не складывать бизнес-логику в компонент вынесите ее в кастомные хуки или отдельные файлы.
  • Не пренебрегать тестами: каждый рефакторинг должен сопровождаться хотя бы одним покрывающим тестом.

Проведя такой рефакторинг, вы получаете более легкое, предсказуемое приложение, где каждый кусок кода отвечает только за одну задачу. Когда проект растет, а требования к производительности становятся жестче, описанные техники помогают сохранить чистоту кода без риска сломать уже работающие фичи.

Готов начать?

Выбери удобный способ связи — напиши напрямую или оставь заявку

Написать в Telegram

Отвечу в течение пары часов