React anti-patterns: 15 ошибок разработчиков, которые ломают поддержку и производительность

Разбираем React anti-patterns: 15 типичных ошибок в state, рендерах, эффектах и архитектуре. С примерами кода, trade-off и ответами для собеседования.

23 марта 2026 г.21 минLexicon Team

Введение

Тема React anti-patterns кажется простой только до первого зрелого проекта. На уровне демо почти любое решение выглядит рабочим, но под нагрузкой и в длинном жизненном цикле продукта ошибки быстро превращаются в дорогие инциденты: нестабильные рендеры, трудноуловимые баги в эффектах, непредсказуемое поведение форм и рост времени на каждую новую фичу.

Хороший ориентир для диагностики — сначала понять, когда компонент в React действительно перерисовывается, а уже потом оптимизировать. Иначе команда лечит симптомы, а не источник.

В статье разберем 15 ошибок разработчиков, которые чаще всего встречаются на проектах и на собеседованиях: где именно нарушается модель данных, почему падает производительность и как исправлять это без бессистемного переписывания.

Больше вопросов в Telegram

Ежедневные разборы и реальные кейсы с интервью.

Подписаться

Что считать anti-pattern в React

Под anti-pattern в React я буду понимать не "код, который мне не нравится", а решение, которое:

  • ухудшает предсказуемость состояния;
  • увеличивает количество скрытых сайд-эффектов;
  • размывает границы ответственности между слоями;
  • ухудшает производительность без явной бизнес-ценности;
  • затрудняет тестирование и безопасные изменения.

Если решение не ломает эти пункты, это часто вопрос стиля команды. Если ломает, цена поддержки начинает расти нелинейно.

Архитектура ошибок: где anti-patterns возникают чаще всего

Большинство React anti-patterns появляются в одном из трех слоев:

1. Слой данных

Здесь ломается источник истины: дублирование state, хранение производных значений, мутации объектов, конкурирующие запросы. Симптом в продукте — рассинхрон интерфейса и данных.

2. Слой рендера

Здесь возникают лишние перерисовки: нестабильные ссылки, неправильные key, тяжелые вычисления прямо в JSX, мемоизация без необходимости. Симптом — лаги, особенно на слабых устройствах.

3. Слой побочных эффектов

Типичные проблемы: "запрос в каждом рендере", неверные зависимости в useEffect, утечки таймеров и подписок, гонки при асинхронной загрузке. Симптом — "иногда работает, иногда нет".

Типичный сценарий отказа выглядит так: ошибка в data flow вызывает лишние рендеры, лишние рендеры повторно запускают эффекты, эффекты дают новую волну сетевых запросов. В пиковые часы это легко превращается в рост задержек API и деградацию UX при этом в логах нет ни одной фатальной ошибки.

React anti-patterns: 15 ошибок разработчиков

1) Дублирование одного и того же состояния в нескольких местах

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

Что делать: определить один источник истины и вычислять остальное как производные значения.

2) Хранение в state того, что можно вычислить из props/state

Например, filteredList кладут в useState, хотя он полностью выводится из items и query. Это создает лишний жизненный цикл и риск устаревших данных.

Что делать: вычислять в рендере или через useMemo, если расчет действительно тяжелый.

3) Мутация state-объектов напрямую

state.user.name = "..." приводит к тому, что React не видит корректного изменения по ссылке, а баги становятся непредсказуемыми.

Что делать: только иммутабельные обновления.

// Плохо
user.name = "Ivan";
setUser(user);

// Лучше
setUser(prev => ({ ...prev, name: "Ivan" }));

4) Использование индекса массива как key для динамических списков

Пока список статичен, это не заметно. После сортировки, вставок и удаления появляются "прыгающие" состояния элементов, сброс фокуса и странное поведение инпутов. Подробно эта проблема разобрана в материале про типичные ошибки с key в React.

Что делать: использовать стабильный идентификатор доменной сущности.

5) Слепое копирование props в state

Паттерн const [value, setValue] = useState(props.value) без явной причины часто приводит к рассинхрону между родителем и дочерним компонентом.

Что делать: либо сделать компонент контролируемым, либо четко ограничить момент инициализации.

6) Смешивание UI-логики и бизнес-правил в одном компоненте на 500 строк

Такой компонент сложно тестировать, переиспользовать и ревьюить. Ошибка в бизнес-логике приводит к непредсказуемым визуальным последствиям.

Что делать: выносить доменные правила в функции/хуки/сервисы, а компонент оставлять слоем представления. В сложных кейсах помогает структура через compound components pattern.

7) useEffect как универсальный молоток для любой задачи

Если эффектом пытаются "синхронизировать все со всем", появляется каскад обновлений и циклы перерендера.

Что делать: сначала проверить, нужен ли эффект вообще. Многие операции должны происходить в обработчиках событий или вычисляться в рендере.

8) Неполные или ложные зависимости в useEffect

Частая ситуация: разработчик убирает зависимость ради тишины линтера. Код "работает", пока не сталкивается с устаревшим замыканием.

function Search({ query }: { query: string }) {
  const [result, setResult] = useState<string[]>([]);

  useEffect(() => {
    let cancelled = false;

    (async () => {
      const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
      const data = await res.json();
      if (!cancelled) setResult(data.items);
    })();

    return () => {
      cancelled = true;
    };
  }, [query]);

  return <Results items={result} />;
}

Что делать: уважать модель зависимостей, а не обходить ее вручную.

9) Отсутствие отмены асинхронных операций

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

Что делать: AbortController, флаги отмены или библиотека с управлением запросами.

10) Преждевременная мемоизация всего подряд

useMemo и useCallback сами имеют цену: дополнительная сложность и память. На небольших компонентах это часто дает нулевую или отрицательную выгоду. Хорошая проверка — сначала снять профиль в React DevTools и профилировщике.

Что делать: мемоизировать только после измерений.

11) Контекст как глобальное хранилище для всего состояния

Когда в один Context объединяют все поля, каждое изменение триггерит большой каскад ререндеров.

Что делать: дробить контексты по зонам ответственности или использовать специализированный менеджер состояния. Сравнение подходов есть в статье Redux vs Zustand vs Context.

12) Неправильная граница controlled/uncontrolled форм

Часть полей контролируемая, часть нет, плюс ручные ref-манипуляции. В итоге валидация и UX становятся нестабильными.

Что делать: выбрать стратегию для формы и соблюдать ее. Базовые принципы разобраны в controlled vs uncontrolled компоненты.

13) Игнорирование ошибок рендера и отсутствие Error Boundary

Один сбой в виджете может обрушить целую страницу.

Что делать: ставить ErrorBoundary на уровне экранов и критичных виджетов. Подход подробно объяснен в разборе Error Boundaries.

14) Логика конкурентного рендера написана так, будто React работает синхронно

С переходом на современные возможности React важно учитывать конкурентную модель обновлений, иначе возникают визуальные "скачки" и гонки при взаимодействии.

Что делать: понимать приоритеты обновлений и корректно разделять срочные/несрочные задачи. Для углубления полезен материал про concurrent rendering в React.

15) Отсутствие инженерной дисциплины вокруг anti-patterns

Даже если команда знает проблемы, без правил они возвращаются: нет линтеров, нет код-ревью чек-листов, нет мониторинга hot path.

Что делать: закрепить правила в процессе, а не только в головах разработчиков.

Сравнение подходов: быстрый фикс против системного исправления

КритерийБыстрый фикс в компонентеСистемное исправление (архитектурное)
Время внедренияБыстро, часто в рамках одного PRДольше, требует декомпозиции
Риск регрессииНизкий локально, высокий в будущемНиже в долгой перспективе
Влияние на производительностьТочечное, не всегда заметноеСтабильное улучшение на уровне сценариев
ПоддерживаемостьОбычно не растетЗаметно улучшается
ТестируемостьЧасто остается слабойУлучшается за счет разделения слоев
Онбординг новых разработчиковКонтекст фрагментированПравила и границы понятнее
Повторяемость решенияПлохо переносится в другие модулиМасштабируется на весь проект

Практика показывает: hotfix нужен, когда горит бизнес-метрика сегодня. Но если один и тот же класс ошибок повторяется, дешевле перейти к системному решению.

Production pitfalls: как anti-patterns проявляются в реальных системах

Питфолл 1. Лавинообразные ререндеры после "невинного" рефактора

Признак: p95 времени интеракции растет, а CPU в профиле уходит в рендер дерева, где изменений почти нет.

Причина: нестабильные объекты/колбэки в props и слишком широкий контекст.

Профилактика: профилировать ключевые экраны на каждом релизе, иметь бюджет на рендер.

Питфолл 2. Гонки запросов и "мигающие" данные

Признак: пользователь видит то старые, то новые значения после быстрого переключения фильтров.

Причина: нет отмены запросов и контроля версии ответа.

Профилактика: единый слой работы с серверным состоянием, cancelation и дедупликация запросов.

Питфолл 3. Непредсказуемое поведение списков и форм

Признак: теряется фокус, сбрасываются инпуты, выбранный элемент "переезжает".

Причина: неправильные key, смешение controlled/uncontrolled, мутации.

Профилактика: линт-правила, компонентные тесты для списков, ревью по чек-листу состояния.

Прокачай React за 7 дней

20 вопросов и разборов по React Hooks.

Начать

Разбор производительности: где искать узкие места

В теме anti-patterns полезно смотреть не на одну метрику, а на связку:

  • Web Vitals (INP, LCP) для пользовательского ощущения;
  • время рендера и commit в React Profiler;
  • количество ререндеров горячих компонентов;
  • p95/p99 latency для критичных API-сценариев;
  • время выполнения тяжелых селекторов и форматтеров на клиенте.

Отдельный важный компромисс: иногда устранение anti-pattern в данных (например, нормализация и единый источник истины) дает больший эффект, чем оптимизация рендера. Поэтому порядок обычно такой:

  1. Исправить data flow.
  2. Измерить заново.
  3. Только потом применять мемоизацию и микроправки рендера.

Best practices: как не возвращаться к тем же ошибкам

Архитектурные практики

  • Единый источник истины для каждой сущности.
  • Явные границы между UI, доменной логикой и доступом к данным.
  • Контексты только для действительно кросс-срезовых задач.

Практики кода

  • Иммутабельные обновления по умолчанию.
  • Стабильные key из доменных ID.
  • useEffect только для реальных сайд-эффектов, а не как замена вычислений.

Практики наблюдаемости

  • Базовый профиль производительности для ключевых экранов.
  • Оповещения о деградации INP/p95 интеракций.
  • Регулярный разбор ререндеров после крупных релизов.

Практики тестирования

  • Тесты на сценарии изменения списка (вставка, удаление, сортировка).
  • Тесты на гонки запросов и отмену.
  • Проверка деградации UI при ошибках через Error Boundary.

Практики внедрения и отката

  • Фича-флаги для рискованных рефакторинга управления состоянием.
  • Поэтапный rollout на часть аудитории.
  • Быстрый путь отката без отката всего релиза.

Частые ошибки

Короткий чек-лист для ревью:

  • храните ли вы производные данные в state без причины;
  • есть ли в списках key={index} там, где список может меняться;
  • запускает ли useEffect запросы без отмены;
  • есть ли мутации объектов, пришедших из state/props;
  • мемоизируется ли код без измеренной проблемы;
  • превращен ли Context в универсальный "мешок";
  • можно ли прочитать компонент без прокрутки на несколько экранов.

Если на 2-3 пункта ответ положительный, почти всегда стоит оформить технический долг как отдельную задачу спринта.

Как отвечать на интервью

Сильный ответ на вопрос про React anti-patterns обычно строится по схеме:

  1. Дать определение через последствия: "anti-pattern — это решение, которое системно ухудшает предсказуемость, производительность или поддержку".
  2. Назвать 2-3 типовые ошибки: дублирование state, неверные key, злоупотребление useEffect.
  3. Показать, как вы диагностируете проблему: Profiler, метрики, воспроизводимый сценарий.
  4. Объяснить порядок исправления: сначала источник данных и границы ответственности, потом оптимизация рендера.
  5. Добавить компромисс: где достаточно hotfix, а где нужен системный рефакторинг.

Ниже пример ответа с конкретикой:

В React я считаю anti-pattern не по стилю, а по эффекту на систему.
Если решение создает второй источник истины, ломает предсказуемость эффектов
или увеличивает количество лишних ререндеров, это уже инженерная проблема.
Сначала я фиксирую data flow и эффекты, затем проверяю профилирование,
и только потом добавляю мемоизацию там, где есть подтвержденный bottleneck.

Подготовься к React-собеседованию на реальных технических кейсах

Разберем anti-patterns, рендеры, эффекты и архитектурные trade-off в формате mock-интервью с разбором сильных и слабых ответов.

Начать тренировку

FAQ

Чем anti-pattern отличается от обычной "неидеальности" кода

Неидеальность может быть локальной и дешевой в поддержке. Anti-pattern обычно масштабирует проблему: чем больше кодовая база, тем выше стоимость изменений и вероятность регрессии.

Какие три ошибки в React самые дорогие в продакшене

Обычно это дублирование состояния, гонки в асинхронных эффектах и неправильные key в динамических списках. Эти ошибки одновременно бьют и по UX, и по времени разработки.

Когда не стоит немедленно рефакторить anti-pattern

Когда участок не является hot path и нет реального влияния на продуктовые метрики. В таком случае фикс можно запланировать и сделать вместе с соседними задачами, чтобы не дробить контекст команды.

Нужен ли отдельный state manager, чтобы избежать anti-patterns

Не всегда. Небольшое приложение может быть стабильным и на локальном состоянии плюс контекстах. Менеджер состояния нужен, когда появляется сложная синхронизация между экранами и серверным кэшем.

Как убедить команду убрать anti-pattern, если "и так работает"

Показывать измеримые последствия: лишние ререндеры, рост времени интеракций, регрессии в одном и том же модуле. Разговор на языке метрик работает лучше, чем аргументация вкусовыми предпочтениями.

Итоги

React anti-patterns — это не академическая тема, а вопрос экономии времени команды и стабильности продукта. Ошибки в состоянии, эффектах и рендерах редко ограничиваются одним компонентом: они формируют долг, который растет с каждым релизом.

Рабочая стратегия простая: зафиксировать единый источник истины, почистить эффекты, наладить наблюдаемость и только затем оптимизировать рендер. Такой порядок дает предсказуемый результат и на собеседовании, и в реальном продакшене.

Больше вопросов в Telegram

Ежедневные разборы и реальные кейсы с интервью.

Подписаться

Автор

Lexicon Team

Читайте также