Рефакторинг кода в React 2025: практический подход
Как улучшить React-приложение в 2025 году: профилирование, хуки, memo и тесты.
Если ваш список элементов начал лагать, а консоль полна предупреждений о лишних ререндеров, значит пришло время рефакторинга. Важно сразу понять, где находятся узкие места и как их устранить, не нарушив существующий функционал.
1. Поймать “тормоза”: где начинается оптимизация
Первый шаг выяснить, что именно тормозит приложение. Самый простой способ включить DevTools → Profiler и посмотреть, какие компоненты перерисовываются чаще всего. Часто виноваты неоптимальные props или слишком “толстые” функции внутри компонента. Здесь собираем метрики, формируем план и продолжаем.
Tip: Если видите, что один и тот же компонент рендерится 20-30 раз за секунду без видимых изменений это сигнал к действию.
Что искать в профайлере
- Commit count количество render-ов.
- Render duration время, затраченное на каждый рендер.
- Component name имя наиболее “тяжелого” компонента.
Эти метрики позволяют построить небольшую таблицу проблемных участков.
Компонент | Кол-во ререндеров | Среднее время (ms) | Причина |
---|---|---|---|
UserList | 45 | 12 | Передается новый массив users каждый раз |
Chart | 30 | 25 | Вычисления без useMemo |
Sidebar | 20 | 8 | Передается объект 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
только для действительно дорогих компонентов. - Не складывать бизнес-логику в компонент вынесите ее в кастомные хуки или отдельные файлы.
- Не пренебрегать тестами: каждый рефакторинг должен сопровождаться хотя бы одним покрывающим тестом.
Проведя такой рефакторинг, вы получаете более легкое, предсказуемое приложение, где каждый кусок кода отвечает только за одну задачу. Когда проект растет, а требования к производительности становятся жестче, описанные техники помогают сохранить чистоту кода без риска сломать уже работающие фичи.