Async Race - SPA для управления коллекцией машин и проведения drag-race соревнований. Приложение взаимодействует с REST API сервером: запускает и останавливает двигатели, анимирует заезды в реальном времени и фиксирует победителей в таблице рекордов.
Задача: построить полноценный SPA с роутингом, стейтом и компонентным подходом, не используя React, Angular или любые UI-библиотеки и сторонние библиотеки, упрощающие жизнь в целом. Весь интерфейс генерируется через TypeScript.
Чтобы решить эту задачу архитектурно чисто, я спроектировал собственный слой абстракций: базовые классы для создания элементов и страниц, классовые компоненты, сервисный слой и централизованный стор - по сути небольшой фреймворк поверх нативного DOM API.
- 🚗 Garage - CRUD операции с машинами: имя, цвет из RGB-палитры, превью SVG-иконки в выбранном цвете
- ⚡ Генерация - создание 100 случайных машин одной кнопкой
- 🏁 Гонка - одновременный запуск всех машин на странице, конкурентные запросы к API двигателей
- 🛑 Управление двигателем - старт и стоп для каждой машины отдельно; корректная обработка обрыва соединения (500 от API)
- 🏆 Winners - таблица победителей с количеством побед и лучшим временем; сортировка по обоим параметрам
- 📄 Пагинация - 7 машин на странице в гараже, 10 победителей в таблице
- 🔄 Persistent state - состояние инпутов сохраняются при переключении между страницами
Конкурентная гонка
Нужно запустить все машины одновременно и поймать первого финишировавшего.
Решение: Promise.all для старта всех двигателей, Promise.any для победителя.
Обрыв анимации
При 500 от API машина должна остановиться в точке обрыва, а не прыгнуть назад.
Решение: фиксируем текущую позицию через getComputedStyle до сброса transition.
Реактивный UI без фреймворка
Нужно перерисовывать только нужную часть интерфейса при изменении стейта.
Решение: Observer pattern вручную с двумя независимыми каналами - garage и winners.
Отсутствие фреймворка - это не ограничение, а архитектурная задача.
core/templates - два базовых класса:
Creator- единая точка создания DOM-элементов с типизированными параметрами, без дублированияdocument.createElementPage- базовый класс страницы с интерфейсомrenderиdestroy; роутер работает через него Классовые компоненты - каждый компонент инкапсулирует разметку и логику обновления DOM. Данные получают явно через параметры, не читают стор напрямую.
core/services - типизированный API-слой. Компоненты не знают об HTTP - только вызывают методы сервиса.
app/store.ts - централизованный стейт с Observer pattern: подписка через subscribe, обновление через notify. Два независимых канала - garage и winners - чтобы перерисовывать только нужную часть UI.
Роутинг - через hashchange и load без сторонних библиотек. При переходе вызывается destroy() текущей страницы и рендерится новая.
- Vanilla TypeScript - без
any, type assertions и non-null assertions - Vite - сборка и dev-сервер
- SCSS - стилизация
- ESLint (Unicorn) + Prettier - линтинг и форматирование
- Husky + lint-staged - проверки перед каждым коммитом
src/
├── app/
│ ├── index.ts # Точка входа, инициализация роутера
│ └── store.ts # Централизованный стейт
│
├── core/
│ ├── components/ # Компоненты
│ ├── services/ # API-слой, генератор машин, SVG-спрайты
│ └── templates/ # Базовые классы Creator и Page
│
├── pages/
│ ├── garage/ # View Garage: управление машинами + гонка
│ │ ├── carsControl/ # панель создания/редактирования
│ │ └── carsList/ # список с анимацией заездов
│ ├── winners/ # Таблица победителей с сортировкой
│ └── error/ # 404
│
├── types.ts # Глобальные типы
└── style.scss # Глобальные стили
Деплой уже развернут - нужно только запустить сервер локально - PORT 3000
Для запуска нужно:
1. Клонировать сервер
git clone https://github.com/mikhama/async-race-api.gitcd async-race-api
npm install
npm startСервер поднимется на http://localhost:3000
Deploy → https://artem-webdeveloper.github.io/async-race/
-
Клонировать фронтенд и установить зависимости:
git clone https://github.com/Artem-WebDeveloper/async-race.git
cd async-race
npm install- Создать
.envфайл в корне проекта:
VITE_API_URL=http://127.0.0.1:3000- Запустить в режиме разработки:
npm run devMIT © 2026 Artem Gapich