React: практический курс современной фронтенд‑разработки

Практико-ориентированный курс по React для создания интерактивных веб-приложений. Вы пройдете путь от базовых концепций до продвинутых паттернов, состояния, маршрутизации, работы с API, оптимизации и тестирования, собрав полноценное приложение, близкое к реальным продуктам.

1. Старт проекта: Tooling, Vite, структура, TypeScript, линтинг

Старт проекта: Tooling, Vite, структура, TypeScript, линтинг

В этом курсе мы будем писать реальные React‑приложения: с типизацией, единым стилем кода, быстрым запуском и структурой, которую удобно поддерживать. Эта статья — про то, как правильно начать проект, чтобы дальше не тратить время на хаос в папках, несовместимые настройки и «плавающие» ошибки.

Что такое tooling и почему это важно

Tooling — это набор инструментов вокруг кода, которые помогают:

  • быстро запускать проект локально
  • собирать проект для продакшена
  • проверять типы (TypeScript)
  • поддерживать стиль кода и искать ошибки автоматически (линтеры)
  • держать единые правила для всей команды
  • React сам по себе — это библиотека UI. Для полноценной разработки нужны инструменты вокруг него.

    Почему в курсе используем Vite

    Vite — современный сборщик и dev‑сервер для фронтенда.

    Причины выбора:

  • быстрый старт dev‑сервера
  • быстрая горячая перезагрузка (HMR) при изменениях
  • простая конфигурация для большинства проектов
  • отличная поддержка TypeScript и React
  • Если вы раньше видели Create React App, важно знать: CRA больше не является рекомендуемым способом начать новый проект в экосистеме React. В практике чаще используют Vite/Next.js/Remix, а для учебного практического курса Vite — оптимальный баланс скорости и простоты.

    Требования к окружению

    Перед началом установите:

  • Node.js (лучше LTS)
  • один менеджер пакетов: npm/pnpm/yarn
  • Проверить версии:

    Менеджеры пакетов:

  • npm поставляется вместе с Node.js
  • pnpm часто выбирают за скорость и экономию места
  • Yarn тоже распространён
  • Далее в примерах будет npm, но команды легко адаптируются.

    Создаём проект на Vite + React + TypeScript

    Создание проекта:

    Что вы получите:

  • React‑приложение
  • TypeScript настроен из коробки
  • базовая структура проекта
  • dev‑сервер на локальном адресе (Vite покажет его в консоли)
  • Полезно знать основные команды:

  • npm run dev — запуск в режиме разработки
  • npm run build — сборка для продакшена
  • npm run preview — локальный просмотр собранной версии
  • Разбираем базовую структуру проекта

    Типичная структура Vite React TS проекта выглядит так:

    Ключевые элементы:

  • index.html — точка входа для Vite (он подключает ваш JS/TS бандл)
  • src/main.tsx — место, где React «встраивается» в страницу и рендерит приложение
  • src/App.tsx — корневой компонент приложения
  • vite.config.ts — конфигурация Vite
  • tsconfig.json — конфигурация TypeScript
  • Предлагаемая структура для учебного проекта

    Чтобы по мере роста проекта не возник «компонентный свалник», зафиксируем структуру, которой будем придерживаться в курсе.

    !Рекомендуемая структура папок и направление зависимостей

    Рекомендуемый вариант:

    Коротко о назначении папок:

  • app/ — сборка приложения: роутинг, провайдеры (контекст, стор), глобальные стили
  • pages/ — страницы (экранные компоненты, привязанные к маршрутам)
  • widgets/ — крупные блоки UI, которые собирают несколько фич
  • features/ — пользовательские сценарии: «поиск», «логин», «добавить в избранное»
  • entities/ — сущности предметной области: «User», «Product», «Article» (UI + типы + запросы)
  • shared/ — переиспользуемые кирпичики: UI‑компоненты, утилиты, API‑клиент, конфиги
  • assets/ — статические файлы (картинки, шрифты)
  • Важно правило: зависимости должны быть направлены сверху вниз.

  • shared ни от кого не зависит (или почти ни от кого)
  • entities могут использовать shared
  • features могут использовать entities и shared
  • widgets и pages собирают всё вместе
  • app склеивает приложение как продукт
  • Это защищает от ситуации, когда «базовый компонент» тянет за собой половину приложения.

    TypeScript в React‑проекте: что нужно понимать

    TypeScript — это надстройка над JavaScript, которая добавляет статическую типизацию.

    Простыми словами:

  • вы описываете, какие данные ожидаете
  • TypeScript проверяет это до запуска кода
  • многие ошибки ловятся ещё в редакторе
  • В React TypeScript особенно полезен для:

  • пропсов компонентов
  • событий (например, onChange у input)
  • данных с сервера
  • контрактов между модулями
  • Рекомендуемые настройки strict

    В TypeScript есть режим строгой проверки — strict. Он включает важные проверки, например:

  • запрет неявного any (когда тип «неизвестно какой»)
  • более строгую проверку null/undefined
  • В реальных проектах строгий режим экономит много времени.

    Проверьте в tsconfig.json, что строгая проверка включена (в Vite‑шаблоне обычно уже включено):

    Если страшно включать strict в старом проекте — это нормально. Но в новом учебном проекте лучше начинать правильно.

    Алиасы импортов (удобно и чище)

    Когда проект растёт, импорты вида ../../../../shared/ui/Button становятся проблемой.

    Сделаем алиас, например @src.

    Шаг 1. В tsconfig.json:

    Шаг 2. Чтобы Vite тоже понимал этот алиас, поставим плагин:

  • vite-tsconfig-paths
  • Шаг 3. Подключим его в vite.config.ts:

    Теперь можно писать:

    Линтинг: ESLint и Prettier (и почему это два разных инструмента)

    Линтинг — это автоматическая проверка кода на ошибки и несогласованный стиль.

    Здесь почти всегда используется связка:

  • ESLint — ищет ошибки и проблемные паттерны в коде
  • Prettier — форматирует код единым образом
  • Важно: ESLint и Prettier решают разные задачи.

  • ESLint отвечает за качество и корректность (например, неиспользуемые переменные)
  • Prettier отвечает за оформление (отступы, кавычки, переносы строк)
  • Подключаем ESLint для React + TypeScript

    В Vite‑шаблоне ESLint может быть не настроен. Настроим.

    Устанавливаем зависимости:

    Создаём конфиг ESLint (формат flat config) eslint.config.js:

    Что здесь важно:

  • typescript-eslint позволяет ESLint понимать TypeScript
  • eslint-plugin-react-hooks проверяет правила хуков (мы разберём хуки позже, но линтер защитит вас сразу)
  • eslint-plugin-react-refresh помогает избежать проблем с горячей перезагрузкой в dev‑режиме
  • Добавим скрипт в package.json:

    Запуск:

    Подключаем Prettier

    Устанавливаем:

    Создаём .prettierrc:

    Добавим скрипты:

    Запуск форматирования:

    Чтобы ESLint и Prettier не конфликтовали

    Иногда ESLint пытается спорить с форматированием Prettier. Типовое решение — отключить форматирующие правила ESLint.

    Устанавливаем:

  • eslint-config-prettier
  • И добавляем его в конфиг ESLint. Для flat config это делается импортом и подключением конфигурации:

    Идея простая: Prettier отвечает за формат, ESLint — за правила кода.

    Минимальная дисциплина в репозитории

    Чтобы проект был удобен в работе:

  • договоритесь об одном менеджере пакетов в команде
  • добавьте .gitignore (Vite создаёт его автоматически)
  • держите настройки линтера/форматтера в репозитории
  • фиксируйте структуру src/ и не смешивайте «всё в components»
  • Итог

    Вы создали базу, на которой удобно строить React‑приложение:

  • проект на Vite + React + TypeScript
  • понятная стартовая структура папок
  • строгая типизация (strict)
  • алиасы импортов для чистого кода
  • ESLint для качества и ошибок
  • Prettier для единого форматирования
  • Дальше мы начнём писать компоненты и разбирать фундамент React: JSX, рендеринг, props/state и жизненный цикл через хуки.

    2. JSX и компоненты: props, children, стилизация, дизайн системы

    JSX и компоненты: props, children, стилизация, дизайн системы

    В прошлой статье мы настроили проект: Vite, TypeScript, линтинг, алиасы и базовую структуру папок. Теперь начинаем писать UI по-настоящему: разберём JSX, компоненты и их композицию через props и children, а затем заложим основу дизайн системы (набор переиспользуемых UI-компонентов и правил), чтобы проект рос без хаоса.

    JSX: разметка внутри JavaScript

    JSX — это синтаксис, который позволяет писать разметку, похожую на HTML, прямо в JavaScript/TypeScript. При этом JSX не является строкой и не является HTML: это синтаксический сахар для создания React-элементов.

    Официальный вводный материал React по JSX: Writing Markup with JSX.

    Базовый пример

    Что важно:

  • Внутри JSX можно вставлять выражения через {...}.
  • Вставлять можно именно выражения, а не инструкции. Например, a + b можно, а if (...) {} напрямую нельзя.
  • Выражения в JSX: что можно и что нельзя

    Можно:

    Нельзя:

    Для условий используйте тернарный оператор или логическое && (покажем ниже).

    Правила JSX, которые часто ломают новичков

  • У компонента должен быть один корневой элемент.
  • Любой тег должен быть закрыт.
  • Атрибуты отличаются от HTML:
  • - className вместо class - htmlFor вместо for

    Пример с фрагментом, когда не хочется лишний контейнер:

    Компоненты: функция, которая возвращает UI

    В современном React основной формат — функциональные компоненты. Компонент:

  • принимает входные данные (props)
  • возвращает React-элементы (JSX)
  • Практика, которую будем использовать в курсе:

  • Компоненты называем с заглавной буквы: UserName, Button.
  • Пропсы типизируем.
  • Структуру держим по папкам из прошлой статьи: базовые UI-компоненты — в src/shared/ui.
  • Props: контракт между компонентами

    props — это входные данные компонента. Это контракт, который описывает:

  • какие данные нужны компоненту
  • какие обработчики он вызывает
  • какие варианты отображения он поддерживает
  • Материал React: Passing Props to a Component.

    Props нельзя мутировать

    Props в React следует считать только для чтения. Если попытаться менять их внутри компонента, вы создадите неочевидное поведение.

    Правильно:

    Ограничения inline-стилей:

  • сложнее работать с состояниями вроде :hover
  • нет привычной структуры CSS
  • стили сложно переиспользовать
  • Дизайн система: как не утонуть в компонентах

    Дизайн система — это набор правил и компонентов, который обеспечивает:

  • единообразие UI
  • повторное использование
  • предсказуемое API компонентов
  • быструю сборку новых экранов
  • На уровне нашего учебного проекта дизайн система начнётся с src/shared/ui: кнопки, инпуты, типографика, контейнеры.

    !Из чего состоит дизайн система и как она масштабируется

    Принципы хорошего UI-компонента

  • Контролируемые варианты через variant, size, tone вместо передачи произвольных классов.
  • Композиция через children вместо десятков специальных пропсов.
  • Поддержка нативных атрибутов через ComponentPropsWithoutRef<'...'>.
  • Доступность как поведение по умолчанию: правильные теги (button для кнопки), поддержка disabled, aria-*.
  • Минимальные правила для shared/ui в этом курсе

  • Компонент лежит в отдельной папке.
  • В папке: Component.tsx, Component.module.css, index.ts.
  • Снаружи импортируем только из публичного API папки: import { Button } from '@/shared/ui/Button'.
  • Эти простые правила экономят время, когда компонентов становится десятки.

    Итог

    Теперь у вас есть базовые строительные блоки React-приложения:

  • JSX как способ описывать UI через выражения
  • компоненты как функции, возвращающие React-элементы
  • props как типизированный контракт
  • children как основа композиции
  • условный рендеринг и списки с правильными key
  • стилизация через CSS Modules
  • старт дизайн системы на основе shared/ui
  • В следующих шагах курса мы продолжим собирать интерактивность: состояние, эффекты, обработка событий и архитектура компонентов в реальном приложении.

    3. Состояние и формы: хуки, контролируемые инпуты, валидация

    Состояние и формы: хуки, контролируемые инпуты, валидация

    В предыдущих статьях мы настроили проект (Vite, TypeScript, линтинг, структура) и собрали базовые компоненты (JSX, props, children, стилизация, основы дизайн системы). Теперь добавим интерактивность: научимся хранить и обновлять состояние, строить формы на контролируемых инпутах и делать предсказуемую валидацию.

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

    Что такое состояние в React

    Состояние — это данные, которые:

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

    Ключевой принцип: UI — это функция от состояния.

    Хук useState: базовый инструмент для состояния

    Документация: useState.

    Пример счётчика:

    Почему важно использовать функциональное обновление

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

    Это защищает от ошибок, когда несколько обновлений происходят подряд.

    Что хранить в состоянии, а что вычислять

    Правило практики: храните только то, что нужно запомнить между рендерами и что нельзя надёжно вычислить из других данных.

    Пример: не стоит хранить fullName, если есть firstName и lastName.

    Так меньше риска рассинхронизации.

    Не мутируйте объекты и массивы в состоянии

    React сравнивает значения, и для объектов важно создавать новую ссылку.

    Плохо:

    Хорошо:

    Для массива:

    Когда useReducer удобнее, чем useState

    Документация: useReducer.

    Если состояние сложное: много полей, много переходов, нужно централизованно обрабатывать события формы, useReducer делает код понятнее.

    Мини-пример для формы:

    Идея: все изменения проходят через dispatch, а правила переходов лежат в reducer.

    Контролируемые инпуты: основа форм в React

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

    !Цикл обновления контролируемого поля: состояние задаёт значение, события обновляют состояние

    Пример:

    Плюсы контролируемого подхода:

  • значение всегда известно React
  • легко валидировать
  • легко сбрасывать форму
  • можно синхронизировать UI (например, отключать кнопку отправки)
  • Типизация событий в TypeScript

    В большинстве случаев тип выводится автоматически. Если нужно явно типизировать:

    Но в компонентах чаще удобнее наружу отдавать не событие, а готовое значение:

    Так вы уменьшаете связность с DOM и упрощаете тестирование.

    Чекбоксы и checked

    У чекбокса контролируется не value, а checked:

    Отправка формы: onSubmit и предотвращение перезагрузки

    В React формы отправляются через onSubmit. В браузере форма по умолчанию перезагружает страницу, поэтому в SPA мы делаем preventDefault.

    Практическая рекомендация: кнопку отправки делайте type="submit", а обработчик держите на form, чтобы форма работала и по нажатию Enter.

    Валидация: как сделать предсказуемо

    Валидация — это правила, которые определяют, корректны ли данные. Обычно в форме нужно решить три задачи:

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

    Шаг 1. Функция валидации как чистая логика

    Создадим функцию, которая принимает значения и возвращает объект ошибок.

    Почему это хороший стиль:

  • функцию легко тестировать
  • она не зависит от React
  • её можно переиспользовать и на клиенте, и частично на сервере
  • Шаг 2. Состояния touched и submitted

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

  • touched по полям, чтобы показать ошибку после того, как пользователь взаимодействовал с полем
  • submitted, чтобы при попытке отправки показать ошибки сразу для всех полей
  • Пример формы с валидацией:

    Здесь важно:

  • values обновляется иммутабельно
  • errors вычисляется из values, а не хранится отдельно, чтобы не было рассинхронизации
  • ошибки показываются только после onBlur или первой попытки submit
  • Ошибки сервера и асинхронная валидация

    В реальных приложениях есть ошибки, которые клиент не может проверить сам:

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

    Пример подхода:

    Так UI может:

  • показывать ошибки полей отдельно
  • показывать общую ошибку формы отдельно
  • блокировать повторные отправки через isSubmitting
  • Мини-компонент поля для дизайн системы

    В прошлой статье мы начали дизайн систему в src/shared/ui. Для форм удобно иметь базовый TextField, который:

  • принимает value и onChange(value)
  • отображает label и error
  • поддерживает нативные пропсы input
  • Пример:

    Далее ваша форма становится компактнее, а правила API фиксируются в одном месте.

    Контролируемые и неконтролируемые формы

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

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

    Если хотите познакомиться с инструментарием форм глубже, посмотрите:

  • React Hook Form
  • Zod
  • В этом курсе мы сначала доведём до автоматизма базовый контролируемый подход, чтобы вы понимали, что делают библиотеки под капотом.

    Типичные ошибки в формах

  • Обновлять состояние на основе старого значения без функционального обновления.
  • Хранить и values, и errors как два независимых состояния без строгих правил синхронизации.
  • Мутировать объекты в состоянии.
  • Делать обработчик onClick на кнопке вместо onSubmit на форме.
  • Показывать ошибки сразу при первом рендере, не используя touched или submitted.
  • Итог

    Вы освоили основу интерактивных React-компонентов:

  • состояние через useState и правила его обновления
  • иммутабельные обновления объектов и массивов
  • переход к useReducer, когда состояние становится сложным
  • контролируемые инпуты для форм и типизацию событий
  • отправку форм через onSubmit и preventDefault
  • предсказуемую валидацию через чистую функцию validate, touched и submitted
  • Дальше по курсу мы будем расширять интерактивность: побочные эффекты, работа с API, асинхронные данные и архитектурные паттерны, чтобы формы стали частью полноценного приложения.

    4. Эффекты и работа с данными: API, кеширование, React Query, Suspense

    Эффекты и работа с данными: API, кеширование, React Query, Suspense

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

    В React это почти всегда начинается с эффектов (побочных действий), а в современных проектах быстро переходит к специализированным инструментам для серверного состояния — например, TanStack Query (часто называют React Query).

    Цель темы: научиться строить слой данных так, чтобы UI оставался простым, а приложение — быстрым и предсказуемым.

    Что такое эффект в React

    Рендер React-компонента должен быть чистым: один и тот же вход должен давать один и тот же UI без скрытых побочных действий.

    Эффекты — это код, который взаимодействует с внешним миром:

  • запросы к API
  • подписки (WebSocket, события)
  • таймеры
  • прямые обращения к браузерным API
  • Для эффектов в React используется хук useEffect.

    Документация: useEffect.

    Когда запускается useEffect

  • useEffect выполняется после рендера (после того, как React отрисовал UI).
  • Эффект можно привязать к изменениям конкретных значений через массив зависимостей.
  • Эффект может вернуть функцию очистки, которая выполнится перед следующим запуском эффекта и при размонтировании компонента.
  • !Жизненный цикл эффекта и очистки

    Массив зависимостей: главный источник ошибок

    У useEffect есть форма:

  • Если зависимостей нет, эффект запустится после каждого рендера.
  • Если зависимости пустые [], эффект запустится один раз после первого рендера.
  • Если зависимости перечислены, эффект запустится при изменении хотя бы одной зависимости.
  • Практическое правило: если эффект использует значение из области видимости компонента, оно должно быть в зависимостях, иначе вы рискуете получить устаревшие данные.

    Ручная загрузка данных через fetch и useEffect

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

    Документация: Fetch API.

    Базовый шаблон: данные, загрузка, ошибка

    Почему это полезно:

  • тип T фиксирует контракт данных
  • обработка res.ok централизована
  • компоненты и хуки становятся короче
  • useQuery: чтение данных с кешированием

    Сделаем сущность Post (в реальном проекте это было бы в entities/post).

    postTypes.ts:

    postQueries.ts:

    Использование в компоненте:

    Почему queryKey — это важно

    queryKey должен однозначно описывать запрос. Если запрос зависит от параметров, параметры должны быть частью ключа.

    Пример: список постов по пользователю.

    Если вы забудете userId в ключе, React Query будет считать, что это один и тот же запрос, и вы получите неправильный кеш.

    Настройки кеша, которые важно понимать

    | Опция | Что делает | Практический смысл | | --- | --- | --- | | staleTime | сколько времени данные считаются свежими | снижает количество рефетчей при навигации | | gcTime | как долго неиспользуемые данные живут в кеше | контролирует память и поведение при возврате на экран | | refetchOnWindowFocus | делать ли refetch при возврате фокуса | удобно для админок, но иногда раздражает | | retry | сколько раз повторять запрос при ошибке | помогает при временных сбоях сети |

    Примечание: в актуальных версиях TanStack Query используется gcTime (раньше часто встречали cacheTime).

    useMutation: изменение данных и синхронизация UI

    Если useQuery отвечает за чтение, то useMutation — за запись.

    Пример: создаём пост и обновляем список.

    Использование:

    Инвалидация или прямое обновление кеша

    Самый простой путь — invalidateQueries: сказать React Query, что данные устарели и их надо перезагрузить.

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

    Suspense для загрузки данных

    Suspense — механизм React для декларативного ожидания асинхронных зависимостей. Компонент может “приостановить” рендер, а React покажет fallback.

    Документация: Suspense.

    React Query умеет работать с Suspense.

    Гайд: Suspense в TanStack Query.

    Как это выглядит в UI

    С Suspense вы чаще пишете так:

  • нет ручных isLoading
  • загрузка описана на уровне границы <Suspense fallback={...}>
  • Пример запроса с Suspense:

    Компонент больше не обрабатывает isLoading:

    И оборачиваем в Suspense:

    Важный момент: ошибки и Error Boundary

    Suspense решает ожидание, но не решает обработку ошибок. Для ошибок нужен Error Boundary.

    В React Error Boundary традиционно делается через классовые компоненты, но в современных проектах часто используют готовую библиотеку.

    Реальный вариант: react-error-boundary.

    Идея связки:

  • <Suspense> показывает fallback во время загрузки
  • <ErrorBoundary> показывает UI ошибки, если запрос упал
  • Как держать слой данных аккуратным в структуре проекта

    Свяжем это с архитектурой из первой статьи:

  • shared/api содержит базовые инструменты: httpJson, обработку ошибок, конфиги
  • entities/*/api содержит функции запросов и хуки useQuery/useMutation для конкретной сущности
  • features/* собирают сценарии: форма создания, лайк, поиск
  • widgets/pages подключают фичи и отображают экраны
  • Практическое правило: компоненты shared/ui не должны знать о React Query. Запросы — это слой выше.

    Итог

    Теперь у вас есть рабочая модель работы с данными в современном React-приложении:

  • вы понимаете, что такое эффекты и как корректно использовать useEffect
  • вы умеете загрузить данные вручную и знаете, почему это тяжело поддерживать
  • вы подключили React Query и научились:
  • - читать данные через useQuery и правильно строить queryKey - менять данные через useMutation - синхронизировать UI через инвалидацию кеша
  • вы понимаете роль Suspense и почему для ошибок нужен Error Boundary
  • Дальше логичный шаг — научиться собирать всё это в полноценные экраны: маршрутизация, состояние URL, пагинация, фильтры, а также проектирование компонентов так, чтобы серверное состояние и UI не мешали друг другу.

    5. Маршрутизация и архитектура: React Router, layouts, guard, code splitting

    Маршрутизация и архитектура: React Router, layouts, guard, code splitting

    После работы с формами и серверными данными (эффекты, React Query, Suspense) следующий обязательный шаг для реального приложения — маршрутизация. Почти любое приложение состоит из экранов (страниц), и нам нужно:

  • связывать экраны с URL
  • делать общий каркас (шапка, меню, футер) через layouts
  • ограничивать доступ (guard) на уровне маршрутов
  • ускорять загрузку через code splitting
  • В этой статье мы соберём основу навигации на React Router, встроим её в структуру проекта из первых тем и свяжем с Suspense и границами ошибок.

    Что такое SPA-маршрутизация

    В SPA (single-page application) браузер не перезагружает страницу при переходах. Вместо этого:

  • URL меняется
  • приложение решает, какой компонент показать
  • данные подгружаются асинхронно
  • Маршрутизатор — это слой, который сопоставляет URL и UI.

    !Как URL сопоставляется со страницами внутри SPA

    Установка React Router

    Мы будем использовать React Router v6.

    Официальная документация: React Router.

    Установка:

    Где маршрутизация живёт в архитектуре проекта

    Мы придерживаемся структуры, которую заложили в начале курса:

  • app/ собирает приложение как продукт
  • pages/ хранит экранные компоненты
  • shared/ содержит инфраструктуру и базовые компоненты
  • Практически удобное разбиение для роутинга:

    Идея простая:

  • страницы не должны создавать роутер
  • роутер не должен содержать бизнес-логику страниц
  • доступ (guard) лучше держать рядом с роутером, но саму проверку авторизации — в shared/lib или entities
  • Базовая настройка роутера

    В React Router есть несколько способов создать маршрутизацию. Мы используем современный подход с data routers и createBrowserRouter.

    Документация: createBrowserRouter.

    Создадим провайдер роутера.

    src/app/providers/RouterProvider/RouterProvider.tsx:

    src/app/providers/RouterProvider/router.tsx:

    Подключим это в src/app/App.tsx (и рядом оставим QueryProvider из прошлой темы):

    Здесь важно:

  • роутер подключён один раз на верхнем уровне
  • React Query оборачивает маршруты, чтобы любые страницы могли использовать useQuery
  • Layouts: общий каркас приложения через Outlet

    Layout — компонент-обёртка, который задаёт общую структуру: шапка, меню, контейнер.

    В React Router layout обычно содержит Outlet — место, где будет отображаться текущая страница.

    Документация: Outlet.

    src/app/providers/RouterProvider/ui/AppLayout.tsx:

    Почему NavLink, а не Link:

  • NavLink умеет определять активный маршрут и помогает подсветить текущий пункт меню
  • Документация: NavLink.

    Вложенные маршруты: страницы и подстраницы

    Один из сильнейших паттернов React Router — вложенность маршрутов. Она позволяет сделать структуру URL и UI одинаково вложенной.

    Пример: страница постов и детальная страница поста.

    router.tsx (фрагмент):

    Что означает index: true:

  • это маршрут по умолчанию внутри /posts
  • он сработает на URL /posts
  • Что означает :postId:

  • это параметр URL
  • он будет доступен в компоненте через useParams
  • Документация: useParams.

    src/pages/PostDetailsPage/PostDetailsPage.tsx:

    Практическая рекомендация:

  • параметры URL всегда строки
  • если вам нужен number, делайте явное преобразование и обработку ошибки
  • Состояние URL: query string и useSearchParams

    Кроме параметров пути (/posts/42) часто используются параметры строки запроса: ?q=react&page=2. Это удобно для фильтров, поиска и пагинации.

    Документация: useSearchParams.

    Пример: простой поиск на странице постов.

    src/pages/PostsPage/PostsPage.tsx:

    Зачем хранить фильтры в URL:

  • ссылкой можно поделиться
  • фильтры сохраняются при перезагрузке
  • навигация назад/вперёд работает ожидаемо
  • Guard: защита маршрутов

    Guard (защитник маршрута) — это компонент, который решает, можно ли показывать страницу.

    Типовые случаи:

  • нельзя открыть профиль без авторизации
  • нельзя открыть /login, если уже авторизован
  • нельзя открыть админку без роли
  • Минимальная модель авторизации для примера

    Чтобы показать механику guard, сделаем простую модель. В реальном проекте источник данных об авторизации может быть:

  • токен в памяти и обновление токена
  • запрос me через React Query
  • локальное хранилище
  • Для учебной практики достаточно стора в shared/lib.

    src/shared/lib/auth/authTypes.ts:

    src/shared/lib/auth/authStore.ts:

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

    RequireAuth: доступ только авторизованным

    Для редиректа используем компонент Navigate.

    Документация: Navigate.

    src/app/providers/RouterProvider/guards/RequireAuth.tsx:

    Что здесь происходит:

  • если пользователь не авторизован, мы отправляем его на /login
  • replace заменяет запись в истории браузера, чтобы «назад» не возвращал на защищённую страницу
  • state.from сохраняет, откуда пришли, чтобы после входа вернуть пользователя обратно
  • Использование guard в маршрутах

    Фрагмент router.tsx:

    Guard для страницы входа: нельзя открыть login, если уже вошёл

    Иногда нужно обратное условие.

    src/app/providers/RouterProvider/guards/RequireGuest.tsx:

    И применяем:

    Code splitting: разделение кода по маршрутам

    Когда приложение растёт, один большой бандл начинает мешать:

  • первая загрузка становится дольше
  • пользователю приходится скачивать код страниц, которые он может никогда не открыть
  • Code splitting — это техника, при которой код разбивается на части (чанки) и подгружается по мере необходимости.

    В React базовый инструмент — React.lazy и динамический импорт import().

    Документация: lazy и Suspense.

    Ленивые страницы

    Создадим ленивые импорты для страниц.

    src/app/providers/RouterProvider/router.tsx (фрагмент):

    Почему мы используем .then((m) => ({ default: m.HomePage })):

  • lazy ожидает модуль с default export
  • мы оставляем страницу с именованным экспортом export function HomePage() и адаптируем импорт
  • Практический вывод:

  • либо делайте страницы с export default
  • либо используйте этот адаптер, чтобы сохранить единый стиль именованных экспортов
  • Где ставить Suspense

    Есть два основных варианта:

  • Suspense вокруг каждой страницы
  • один общий Suspense на уровне layout
  • Если вы ставите Suspense внутри layout, то при загрузке любой страницы будет показываться общий fallback, и вы получите более простой роутер.

    AppLayout.tsx (идея):

    Как выбрать:

  • общий Suspense проще
  • отдельные Suspense дают разные лоадеры для разных областей
  • Ошибки маршрутов: not found и ошибки загрузки

    Страница 404

    Добавим маршрут *.

    Error Boundary для страницы

    В прошлой теме мы обсуждали, что Suspense отвечает за ожидание, но ошибки должен обрабатывать Error Boundary.

    React Router также поддерживает обработку ошибок роутов через errorElement (особенно полезно, если вы позже будете использовать loaders/actions).

    Документация: errorElement.

    Пример каркаса:

    Если вы пока не используете loaders, ошибки чаще будут приходить из:

  • React Query (и тогда вы оборачиваете части дерева в Error Boundary)
  • кода компонентов (например, исключение в render)
  • Практика: связываем страницы, данные и архитектуру

    Когда вы строите реальный экран, удобно держать роли слоёв такими:

  • pages/* отвечает за сборку экрана
  • widgets/* — крупные блоки UI (например, список + фильтры)
  • features/* — сценарии (например, «создать пост», «войти»)
  • entities/*/api — запросы и мутации (React Query)
  • shared/ui — компоненты без знания о роутинге и данных
  • !Как маршрутизация и страницы укладываются в архитектуру

    Итог

    Вы собрали основу навигации и структуры приложения:

  • подключили React Router на уровне app/providers
  • сделали layouts через Outlet
  • научились строить вложенные маршруты и читать параметры через useParams
  • научились хранить фильтры в URL через useSearchParams
  • добавили guard-компоненты для защищённых и гостевых маршрутов
  • внедрили code splitting через lazy, import() и Suspense
  • добавили базовую обработку 404 и понимание, где живут ошибки
  • Дальше курс обычно расширяют двумя направлениями: полноценная авторизация (токены, me, обновление сессии) и более сложные сценарии экранов (пагинация, сортировка, синхронизация URL и кеша React Query).

    6. Производительность и качество: мемоизация, ререндеры, accessibility, i18n

    Производительность и качество: мемоизация, ререндеры, accessibility, i18n

    Мы уже научились собирать приложение как продукт: настроили проект и структуру, сделали дизайн систему в shared/ui, научились работать с формами, данными через React Query и навигацией через React Router. Теперь добавим слой, который отличает учебное приложение от продакшена: управляемая производительность и качество пользовательского опыта.

    В этой статье разберём четыре практические темы:

  • как устроены ререндеры и почему они происходят
  • когда и как применять мемоизацию (React.memo, useMemo, useCallback)
  • основы accessibility (a11y): семантика, клавиатура, фокус, ARIA
  • основы i18n: архитектура переводов, форматирование дат/чисел, переключение языка
  • Ререндеры в React: что это и почему это нормально

    Ререндер означает, что React повторно вызывает функцию компонента, чтобы вычислить новое дерево элементов и сравнить его с предыдущим.

    Важно понимать:

  • Ререндер компонента не означает, что браузер перерисует весь DOM.
  • React сначала делает сравнение (reconciliation) и обновляет только то, что реально изменилось.
  • Ререндеры происходят часто, и это нормально. Проблема возникает, когда они становятся дорогими.
  • Основные причины ререндеров

  • обновилось состояние через setState
  • пришли новые props от родителя
  • изменилось значение контекста (useContext)
  • изменились данные React Query, на которые подписан компонент
  • !Диаграмма, показывающая распространение ререндеров по дереву компонентов

    Быстрый способ понять, где проблема

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

  • React DevTools и вкладка Profiler
  • Профилирование производительности в React
  • Мемоизация: зачем она нужна и когда она вредна

    Мемоизация в React помогает избежать лишних пересчётов и лишних ререндеров. Но она не делает приложение автоматически быстрее.

    Практическое правило:

  • сначала добейтесь корректной архитектуры и стабильного UI
  • измерьте проблему (Profiler)
  • применяйте мемоизацию точечно там, где есть реальная стоимость
  • React.memo: предотвращаем ререндер компонента при тех же props

    React.memo оборачивает компонент и пропускает ререндер, если props не изменились по ссылке.

    Когда это полезно:

  • компонент часто ререндерится из-за родителя
  • компонент относительно тяжёлый
  • props обычно стабильны
  • Когда это не поможет:

  • вы каждый раз передаёте новые объекты/функции (новые ссылки)
  • компонент сам подписан на меняющиеся данные (контекст, query)
  • useMemo: мемоизируем результат вычисления

    useMemo нужен не для того, чтобы “не ререндериться”, а чтобы не пересчитывать дорогое значение при каждом ререндере.

    Практические советы:

  • мемоизируйте только то, что действительно дорого
  • зависимости должны быть корректными, иначе получите устаревшие данные
  • useCallback: мемоизируем функцию, чтобы не ломать memo

    useCallback(fn, deps) возвращает стабильную по ссылке функцию между ререндерами, пока не меняются зависимости.

    Важно:

  • handleSave зависит от name, поэтому ссылка будет меняться при изменении name
  • это нормально: стабильность нужна только там, где это даёт выигрыш
  • Типичная причина “лишних ререндеров”: новые объекты в props

    Решения:

  • вынести объект в useMemo
  • сделать пропсы более примитивными
  • Мемоизация и React Query

    React Query и так оптимизирует много вещей (кеш, дедупликация). Чаще всего проблемы производительности в связке с React Query появляются из-за:

  • слишком крупных компонент, где одновременно отображается много данных
  • отсутствия пагинации/виртуализации больших списков
  • пересчётов/сортировок прямо в рендере без useMemo
  • Практика производительности в UI: списки и виртуализация

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

    Подходы:

  • пагинация и бесконечная прокрутка
  • виртуализация списка
  • Популярные библиотеки:

  • react-window
  • TanStack Virtual
  • В учебном проекте достаточно запомнить принцип:

  • если DOM-элементов слишком много, нужно рендерить не всё сразу
  • Accessibility (a11y): качество, которое нельзя “добавить потом”

    Accessibility означает, что интерфейсом удобно пользоваться:

  • с клавиатуры
  • со скринридером
  • при плохом зрении или дальтонизме
  • на разных устройствах и условиях
  • Это не только про людей с инвалидностью. Это про предсказуемость, UX и качество продукта.

    Официальная база: Accessibility в документации React.

    Семантика: правильные теги важнее ARIA

    Сначала используйте правильные элементы:

  • button для клика
  • a для ссылки
  • label для подписи поля
  • Если сделать кликабельный div, вы потеряете:

  • доступность с клавиатуры
  • корректные роли для скринридера
  • ожидаемое поведение
  • Формы: label, error и связь через aria

    Если вы строите TextField в shared/ui, сразу заложите доступность.

    Почему это качественно:

  • label связан с input через htmlFor и id
  • ошибки связаны через aria-describedby
  • aria-invalid помогает ассистивным технологиям
  • Клавиатура: фокус и управление

    Проверьте базовую навигацию:

  • можно ли пройти по интерфейсу через Tab
  • видно ли фокус
  • можно ли активировать элементы через Enter и Space
  • Если вы делаете модальное окно, важно:

  • переводить фокус внутрь модалки при открытии
  • возвращать фокус на элемент-инициатор при закрытии
  • не давать фокусу “уйти” за пределы модалки
  • !Иллюстрация того, как должен вести себя фокус при открытии и закрытии модалки

    ARIA: используйте, когда семантики не хватает

    Рекомендации:

  • не используйте ARIA, если можно решить семантическими тегами
  • если используете ARIA, делайте это осознанно
  • Полезные ссылки:

  • ARIA Authoring Practices Guide
  • MDN: ARIA
  • Автоматическая проверка a11y

    Инструменты:

  • axe DevTools
  • eslint-plugin-jsx-a11y
  • Это хорошо встраивается в дисциплину из первой темы (линтинг и единые правила).

    i18n: интернационализация без хаоса

    i18n отвечает за то, чтобы приложение работало на разных языках и культурах:

  • перевод текста
  • плюрализация
  • форматирование дат, чисел, валют
  • направление текста и особенности локали
  • Архитектурный принцип

    Не храните текст “прямо в компонентах”, если приложение потенциально многоязычное.

    Плохо:

  • строки разбросаны по pages/widgets/features
  • невозможно найти все тексты
  • Хорошо:

  • у вас есть единый механизм t('key')
  • переводы лежат в структурированных словарях
  • Библиотека react-i18next

    Самый распространённый вариант в React:

  • i18next
  • react-i18next
  • Установка:

    Минимальная настройка (пример для учебного проекта).

    src/shared/config/i18n/i18n.ts:

    Подключение в main.tsx:

    Использование в компоненте:

    Переключение языка

    Практический совет:

  • храните выбранный язык в localStorage
  • при инициализации i18n подставляйте сохранённое значение
  • Форматирование дат, чисел и валют через Intl

    Перевода строк недостаточно. Для форматов используйте Intl.

    Это можно держать в src/shared/lib/format и использовать в entities/widgets/pages.

    Как связать качество с архитектурой курса

    Чтобы темы этой статьи не превратились в набор “трюков”, зафиксируем, где это жить в структуре проекта:

  • shared/ui:
  • - доступные компоненты (label, aria, клавиатура) - стабильные API компонентов (контролируемые варианты вроде variant)
  • shared/lib:
  • - форматирование (Intl), утилиты, хелперы
  • shared/config:
  • - конфигурация i18n
  • features/widgets/pages:
  • - мемоизация только там, где профилирование показало проблему - разбиение больших компонентов на более мелкие, чтобы локализовать ререндеры

    Итог

    Вы добавили важный продакшен-слой поверх уже построенного приложения:

  • понимаете природу ререндеров и умеете искать источник обновлений
  • умеете применять мемоизацию по назначению: React.memo, useMemo, useCallback
  • знаете базовые требования accessibility и умеете закладывать их в дизайн систему
  • понимаете основу i18n: ключи переводов, переключение языка, форматирование через Intl
  • Дальше (за пределами этой статьи) логичный шаг курса — интеграция всего вместе в полноценные сценарии: авторизация с реальным API, страницы со сложными фильтрами и пагинацией, синхронизация URL и кеша React Query, а также стабилизация UX через тестирование и e2e.

    7. Тестирование и деплой: RTL, Vitest, e2e, CI, сборка и публикация

    Тестирование и деплой: RTL, Vitest, e2e, CI, сборка и публикация

    Мы уже умеем строить приложение как продукт: структура, TypeScript, дизайн система, формы, React Query, роутинг, производительность, a11y и i18n. Последний шаг, который превращает проект в готовый к работе — это тестирование и деплой.

    В этой статье соберём практический стек:

  • Vitest для unit и component тестов
  • React Testing Library для тестов поведения UI
  • e2e тестирование через Playwright
  • CI пайплайн (проверки на каждый пуш)
  • сборка Vite и публикация (статический хостинг)
  • !Общая картина: что происходит с кодом от коммита до деплоя

    Зачем тесты, если есть TypeScript и линтер

    TypeScript и ESLint закрывают большой класс ошибок, но они не отвечают на главный вопрос: работает ли пользовательский сценарий так, как задумано.

    Тесты дают практическую пользу:

  • фиксируют требования в виде проверок
  • защищают от регрессий при рефакторинге
  • ускоряют изменения в коде без страха сломать соседние части
  • помогают документировать поведение компонентов и фич
  • Типовой набор в современном фронтенде:

  • unit тесты: чистые функции, утилиты, форматирование, валидация
  • component тесты: компонент рендерится, реагирует на действия, показывает состояния
  • integration тесты: несколько компонентов вместе, роутинг, запросы, формы
  • e2e тесты: приложение целиком в браузере, реальные переходы, критические сценарии
  • Vitest как база для тестов

    Vitest — быстрый тест раннер, хорошо интегрируется с Vite и поддерживает современную экосистему.

    Ссылки:

  • Vitest
  • Testing Library
  • Установка

    Что это:

  • vitest запускает тесты
  • jsdom даёт браузерное окружение для компонентных тестов
  • @testing-library/react рендерит React компоненты в тестах
  • @testing-library/jest-dom добавляет удобные матчеры вроде toBeInTheDocument
  • @testing-library/user-event симулирует действия пользователя реалистичнее, чем прямой fireEvent
  • Настройка Vitest

    В vite.config.ts добавим секцию test.

    Создадим src/shared/lib/test/setupTests.ts.

    Добавим скрипты в package.json.

    Практика:

  • локально удобно npm run test в watch режиме
  • в CI почти всегда используйте npm run test:run
  • React Testing Library: тестируем поведение, а не реализацию

    React Testing Library продвигает принцип: тестируйте UI так, как его использует пользователь.

    Документация:

  • React Testing Library Intro
  • Queries About
  • Что считается хорошим запросом

    При поиске элементов используйте приоритет:

  • getByRole с именем
  • getByLabelText для полей
  • getByText для текста
  • Старайтесь избегать:

  • querySelector
  • поиска по классам
  • тестов внутренних функций компонента
  • Потому что цель теста: проверить контракт UI, а не то, как именно он реализован.

    Пример: тестируем компонент кнопки

    Допустим, у вас в shared/ui есть Button (из прошлых тем). Тест:

    Почему это хороший тест:

  • ищем кнопку по роли и тексту
  • кликаем как пользователь
  • проверяем внешнее поведение
  • Пример: тестируем форму с валидацией

    Возьмём логику из темы про формы: показываем ошибки после submit.

    Практика:

  • тестируйте только то, что реально важно пользователю
  • не проверяйте детали вроде количества ререндеров или структуру DOM без необходимости
  • Тестирование компонентов с роутером и React Query

    В нашем курсе почти любой экран завязан на:

  • React Router
  • React Query
  • Значит, нам нужны тестовые обёртки.

    Тестовый QueryClient

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

    MemoryRouter для тестов страниц

    Для роутинга в тестах обычно используют MemoryRouter.

    Документация:

  • MemoryRouter
  • Пример:

    Универсальный render для проекта

    Чаще всего удобно держать одну функцию renderApp в shared/lib/test.

    Как тестировать запросы к API: MSW

    Если компонент использует React Query, вам нужно стабильно контролировать ответы API.

    Есть два популярных подхода:

  • мокать httpJson через vi.mock
  • мокать сеть на уровне fetch через MSW
  • Для приложений с большим количеством запросов удобнее MSW, потому что он приближает тесты к реальному поведению.

    Документация:

  • Mock Service Worker
  • Идея:

  • вы описываете обработчики GET /posts, POST /login
  • React Query делает реальный fetch
  • MSW перехватывает запрос и отдаёт предсказуемый ответ
  • Практическое правило:

  • unit тесты для утилит можно делать без MSW
  • integration тесты виджетов и страниц с React Query чаще проще с MSW
  • e2e тестирование: Playwright

    e2e тесты проверяют приложение целиком в браузере и ловят проблемы, которые сложно увидеть на уровне компонентов:

  • реальные переходы по маршрутам
  • работа сборки и ассетов
  • интеграция форм, запросов, guard, редиректов
  • Мы возьмём Playwright как современный стандарт.

    Документация:

  • Playwright
  • Установка

    Минимальная конфигурация

    playwright.config.ts:

    Почему preview, а не dev:

  • preview запускает приложение так, как оно будет работать после сборки
  • это ближе к продакшену и ловит проблемы сборки
  • В package.json добавим:

    Пример e2e: проверяем роутинг и 404

    e2e/routing.spec.ts:

    Пример e2e: guard и редирект на login

    Практика e2e:

  • e2e должно быть мало, но они должны покрывать критические сценарии
  • не пытайтесь e2e заменить component тесты
  • CI: автоматические проверки на каждый пуш

    CI нужен, чтобы проект оставался в рабочем состоянии независимо от разработчика и машины.

    Документация:

  • GitHub Actions
  • Что обычно запускают в CI для React проекта

  • установка зависимостей
  • линтинг
  • typecheck
  • unit и component тесты
  • сборка
  • иногда e2e
  • Пример GitHub Actions workflow

    Создайте .github/workflows/ci.yml:

    Комментарий про Typecheck:

  • в Vite проектах типы часто проверяют отдельной командой tsc -p tsconfig.json --noEmit
  • если вы хотите чётко разделить, добавьте скрипт typecheck и вызывайте его в CI
  • Пример скрипта:

    И тогда в CI используйте npm run typecheck.

    CI для e2e

    e2e обычно тяжелее и дольше. Есть два сценария:

  • запускать e2e на каждый pull request
  • запускать e2e только на main или перед релизом
  • Пример шага для Playwright:

    Сборка Vite и переменные окружения

    В Vite сборка делается командой vite build.

    Документация:

  • Vite Build
  • Vite Env Variables
  • Команды

  • npm run build создаёт dist
  • npm run preview запускает локальный сервер для dist
  • Переменные окружения

    В Vite переменные для клиента должны начинаться с VITE_.

    Пример .env.production:

    Использование:

    Практика:

  • не храните секреты в VITE_ переменных, они попадут в бандл
  • секреты должны жить на серверной стороне, а не в SPA
  • Деплой: публикация статического приложения

    Vite React приложение можно деплоить как статический сайт, потому что результат сборки — набор файлов в dist.

    Варианты:

  • Vercel
  • Netlify
  • GitHub Pages
  • любой хостинг, который отдаёт статические файлы
  • Важный момент для SPA: fallback на index.html

    Если у вас есть маршруты вроде /posts/42, то при обновлении страницы сервер должен вернуть index.html, чтобы React Router обработал маршрут.

    На платформах это обычно настраивается как rewrite.

    Деплой на Vercel

    Документация:

  • Vercel Deployments
  • Типовые настройки:

  • Build Command: npm run build
  • Output Directory: dist
  • Деплой на Netlify

    Документация:

  • Netlify Build
  • Netlify Redirects
  • Для SPA добавьте файл public/_redirects:

    Vite положит его в dist, и Netlify будет отдавать index.html для любых маршрутов.

    Деплой на GitHub Pages

    GitHub Pages подходит для учебных проектов, но требует учитывать base путь, если сайт публикуется не в корне домена.

    Документация:

  • GitHub Pages
  • Vite Deploy to GitHub Pages
  • Практические правила качества для итогового проекта

    Чтобы проект из курса был похож на реальный продакшен:

  • держите тесты рядом с кодом фич и страниц, но не смешивайте с shared/ui, если это чистые компоненты
  • покрывайте тестами:
  • - критические пользовательские сценарии (логин, создание сущности, навигация) - сложные участки логики (валидации, форматирование, преобразования)
  • не стремитесь к максимальному проценту покрытия, стремитесь к максимальной полезности
  • не делайте e2e на всё, используйте e2e как защиту ключевых потоков
  • в CI обязательно запускайте линтинг, typecheck и vitest run
  • Итог

    Вы закрыли финальный продакшен слой курса:

  • настроили Vitest и React Testing Library для unit, component и integration тестов
  • научились тестировать компоненты, завязанные на React Query и React Router
  • поняли, где уместен MSW для сетевых моков
  • добавили e2e тесты на Playwright для критических сценариев
  • собрали CI пайплайн на GitHub Actions
  • разобрали сборку Vite, переменные окружения и важные нюансы SPA-деплоя
  • Теперь ваш проект не только работает, но и имеет защиту от регрессий и понятный путь до публикации.