React anti-patterns: 15 ошибок разработчиков, которые ломают поддержку и производительность
Разбираем React anti-patterns: 15 типичных ошибок в state, рендерах, эффектах и архитектуре. С примерами кода, trade-off и ответами для собеседования.
- Введение
- Что считать anti-pattern в React
- Архитектура ошибок: где anti-patterns возникают чаще всего
- 1. Слой данных
- 2. Слой рендера
- 3. Слой побочных эффектов
- React anti-patterns: 15 ошибок разработчиков
- 1) Дублирование одного и того же состояния в нескольких местах
- 2) Хранение в state того, что можно вычислить из props/state
- 3) Мутация state-объектов напрямую
- 4) Использование индекса массива как key для динамических списков
- 5) Слепое копирование props в state
- 6) Смешивание UI-логики и бизнес-правил в одном компоненте на 500 строк
- 7) useEffect как универсальный молоток для любой задачи
- 8) Неполные или ложные зависимости в useEffect
- 9) Отсутствие отмены асинхронных операций
- 10) Преждевременная мемоизация всего подряд
- 11) Контекст как глобальное хранилище для всего состояния
- 12) Неправильная граница controlled/uncontrolled форм
- 13) Игнорирование ошибок рендера и отсутствие Error Boundary
- 14) Логика конкурентного рендера написана так, будто React работает синхронно
- 15) Отсутствие инженерной дисциплины вокруг anti-patterns
- Сравнение подходов: быстрый фикс против системного исправления
- Production pitfalls: как anti-patterns проявляются в реальных системах
- Питфолл 1. Лавинообразные ререндеры после "невинного" рефактора
- Питфолл 2. Гонки запросов и "мигающие" данные
- Питфолл 3. Непредсказуемое поведение списков и форм
- Разбор производительности: где искать узкие места
- Best practices: как не возвращаться к тем же ошибкам
- Архитектурные практики
- Практики кода
- Практики наблюдаемости
- Практики тестирования
- Практики внедрения и отката
- Частые ошибки
- Как отвечать на интервью
- FAQ
- Чем anti-pattern отличается от обычной "неидеальности" кода
- Какие три ошибки в React самые дорогие в продакшене
- Когда не стоит немедленно рефакторить anti-pattern
- Нужен ли отдельный state manager, чтобы избежать anti-patterns
- Как убедить команду убрать anti-pattern, если "и так работает"
- Итоги
Введение
Тема 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 в данных (например, нормализация и единый источник истины) дает больший эффект, чем оптимизация рендера. Поэтому порядок обычно такой:
- Исправить data flow.
- Измерить заново.
- Только потом применять мемоизацию и микроправки рендера.
Best practices: как не возвращаться к тем же ошибкам
Архитектурные практики
- Единый источник истины для каждой сущности.
- Явные границы между UI, доменной логикой и доступом к данным.
- Контексты только для действительно кросс-срезовых задач.
Практики кода
- Иммутабельные обновления по умолчанию.
- Стабильные
keyиз доменных ID. useEffectтолько для реальных сайд-эффектов, а не как замена вычислений.
Практики наблюдаемости
- Базовый профиль производительности для ключевых экранов.
- Оповещения о деградации INP/p95 интеракций.
- Регулярный разбор ререндеров после крупных релизов.
Практики тестирования
- Тесты на сценарии изменения списка (вставка, удаление, сортировка).
- Тесты на гонки запросов и отмену.
- Проверка деградации UI при ошибках через Error Boundary.
Практики внедрения и отката
- Фича-флаги для рискованных рефакторинга управления состоянием.
- Поэтапный rollout на часть аудитории.
- Быстрый путь отката без отката всего релиза.
Частые ошибки
Короткий чек-лист для ревью:
- храните ли вы производные данные в
stateбез причины; - есть ли в списках
key={index}там, где список может меняться; - запускает ли
useEffectзапросы без отмены; - есть ли мутации объектов, пришедших из state/props;
- мемоизируется ли код без измеренной проблемы;
- превращен ли Context в универсальный "мешок";
- можно ли прочитать компонент без прокрутки на несколько экранов.
Если на 2-3 пункта ответ положительный, почти всегда стоит оформить технический долг как отдельную задачу спринта.
Как отвечать на интервью
Сильный ответ на вопрос про React anti-patterns обычно строится по схеме:
- Дать определение через последствия: "anti-pattern — это решение, которое системно ухудшает предсказуемость, производительность или поддержку".
- Назвать 2-3 типовые ошибки: дублирование state, неверные
key, злоупотреблениеuseEffect. - Показать, как вы диагностируете проблему: Profiler, метрики, воспроизводимый сценарий.
- Объяснить порядок исправления: сначала источник данных и границы ответственности, потом оптимизация рендера.
- Добавить компромисс: где достаточно 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
Читайте также
frontend
System design для frontend разработчика: как мыслить системно, а не только компонентами
Практический разбор system design для frontend разработчика: границы ответственности, данные, производительность, ошибки и сильный ответ на интервью.
frontend
React архитектура больших приложений: как не утонуть в слоях, состояниях и фичах
Практический разбор React архитектуры больших приложений: слои, feature-модули, state management, performance, ошибки и критерии выбора.
frontend
Типичные ошибки на React-интервью: где теряют баллы и как отвечать сильнее
Разбираем типичные ошибки на React собеседовании: шаблонные ответы, провалы в useEffect, state management, производительности и архитектуре. Показываем, что именно раздражает интервьюера и как переформулировать ответ сильнее.