Стенд к статье «пошКОДим: как превратить React-компонент в неуправляемый комбайн — 15 вредных советов».
Один компонент карточки клиента CRM прожил 15 вредных советов: каждый шаг — собираемый и запускаемый снапшот в src/steps/, от 123 строк кода до комбайна с AST-счётом ветвлений 116. По мере деградации появляются воспроизводимые дефекты. Рядом — распиленная версия (src/good/); один общий набор поведенческих тестов подтверждает совпадение поведения в 12 зафиксированных сценариях.
pnpm install
pnpm dev # playground: слайдер v00 → v15 → good, вкладки «Диф» и «Счётчик»
pnpm lint # поддерживаемый код стенда; намеренно плохие снапшоты исключены
pnpm test # 46 тестов: 12 поведенческих × 2 реализации + smoke и оболочка
pnpm report # метрики + дифы + графики → report/В playground:
- слайдер переключает шаг деградации — на каждом шаге живое приложение (роли, форма, экспорт CSV, фейковый бэкенд с латентностью);
- «Диф шага» — что именно добавил совет (генерируется сравнением папок-снапшотов, история не завязана на git-коммиты);
- «Счётчик деградации» — метрики шага и дельта к предыдущему;
- deep-link сохраняет шаг и вкладку:
?step=v15&tab=metricsоткрывает финальный счёт напрямую.
Все значения счётчика и графиков лежат в report/ и воспроизводятся командой pnpm report:
| Число из статьи | Где лежит | Как считается |
|---|---|---|
| Строки кода шага | report/metrics.json → codeLines |
без пустых строк и комментариев |
| Хуки, пропсы | hooksTotal, propsCount |
AST TypeScript: вызовы use*, члены интерфейсов *Props |
| AST-счёт ветвлений компонента | maxComplexity |
авторский сигнал стенда: ветвления + &&/` |
| Условия в JSX | jsxConditions |
тернарники и логические операторы внутри JSX |
| Цена монтирования | mountCost |
LOC одинаково оформленного файла mounts/vNN.mount.tsx; число зависит от форматирования и выбранных test helper'ов, эти же файлы гоняет smoke |
| Дифы шагов | report/diffs/vNN.diff |
git diff --no-index между соседними снапшотами |
| Графики | report/charts/*.svg |
воспроизводимые исходники из metrics.json; для Habr растрируются в PNG из images/ |
src/steps/v00-baseline … v15-final # 16 снапшотов; совет NN статьи = папка vNN
src/good/ # обезвреживание: api/ domain/ permissions/ form/ ui/
src/app/ # playground: слайдер, дифы, счётчик
spec/ # фейковый бэкенд + общие поведенческие тесты + smoke
mounts/ # «цена монтирования»: по файлу на снапшот
rig/ # metrics · diff · chart · report (Node, без зависимостей)
report/ # сгенерированные числа — источник всех цифр статьи
Соответствие: совет NN статьи ↔ src/steps/vNN-* ↔ позиция NN слайдера ↔ строка metrics.json ↔ report/diffs/vNN.diff.
| Шаг | Совет | Акт |
|---|---|---|
| v01 | «Зачем тебе отдельный API-слой» | I. Данные |
| v02 | «Храни как пришло» | I |
| v03 | «Заказы? Ещё один fetch рядом» | I |
| v04 | «Сам себе кэш» | I |
| v05 | «useState на каждое поле» | II. Форма |
| v06 | «Производное тоже храни» | II |
| v07 | «Валидация здесь же» | II |
| v08 | «Ошибки сервера — в тот же объект» | II |
| v09 | «Права проверяй в JSX» | III. Роли |
| v10 | «Новый вариант — новый boolean-проп» | III |
| v11 | «Дели на renderSection()» | III |
| v12 | «Свяжи поля магическими строками» | III |
| v13 | «Оберни всё в memo» | IV. Косметика |
| v14 | «try/catch и console.error» | IV |
| v15 | «Наведи порядок комментариями» | IV |
Фразы в PR, по которым узнают растущий комбайн (подробности — в статье):
- «пока положил рядом, потом вынесем» → когда «потом»? (советы 01, 07)
- «просто ещё один useState» → меняется ли он согласованно с существующими? (05, 06)
- «добавил флажок, чтобы не плодить компоненты» → какие комбинации флажков осмысленны? (10)
- «разбил на renderX для читаемости» → какой у секции контракт? (11)
- «сделал универсальный обработчик» → что случится при переименовании поля? (12)
- «обернул в memo, стало быстрее» → есть замер? пропсы стабильны? (13)
- «добавил try/catch, чтобы не падало» → кто узнаёт об ошибке? (14)
- «причесал файл, добавил секции» → почему секция — не файл? (15)
Машинная страховка: max-lines-per-function и complexity в ESLint как сигнализация с осознанными порогами.
React 19.2 · TypeScript (strict) · Vite · Vitest + Testing Library. Метрики — свой AST-счётчик на компиляторе TypeScript, без внешних зависимостей. Версии окружения фиксируются в report/env.json.