Стек: HTML, SCSS, TS, Vite
Структура проекта:
- src/ — исходные файлы проекта
- src/components/ — папка с JS компонентами
- src/components/base/ — папка с базовым кодом
Важные файлы:
- index.html — HTML-файл главной страницы
- src/types/index.ts — файл с типами (включая типы событий приложения)
- src/main.ts — точка входа приложения
- src/scss/styles.scss — корневой файл стилей
- src/utils/constants.ts — файл с константами
- src/utils/utils.ts — файл с утилитами
Для установки и запуска проекта необходимо выполнить команды
npm install
npm run dev
или
yarn
yarn dev
npm run build
или
yarn build
«Web-Larёk» — это интернет-магазин с товарами для веб-разработчиков, где пользователи могут просматривать товары, добавлять их в корзину и оформлять заказы. Сайт предоставляет удобный интерфейс с модальными окнами для просмотра деталей товаров, управления корзиной и выбора способа оплаты, обеспечивая полный цикл покупки с отправкой заказов на сервер.
Код приложения разделен на слои согласно парадигме MVP (Model-View-Presenter), которая обеспечивает четкое разделение ответственности между классами слоев Model и View. Каждый слой несет свой смысл и ответственность:
Model - слой данных, отвечает за хранение и изменение данных. View - слой представления, отвечает за отображение данных на странице. Presenter - презентер содержит основную логику приложения и отвечает за связь представления и данных.
Взаимодействие между классами обеспечивается использованием событийно-ориентированного подхода. Модели и Представления генерируют события при изменении данных или взаимодействии пользователя с приложением, а Презентер обрабатывает эти события используя методы как Моделей, так и Представлений.
Является базовым классом для всех компонентов интерфейса.
Класс является дженериком и принимает в переменной T тип данных, которые могут быть переданы в метод render для отображения.
Конструктор:
constructor(container: HTMLElement) - принимает ссылку на DOM элемент за отображение, которого он отвечает.
Поля класса:
container: HTMLElement - поле для хранения корневого DOM элемента компонента.
Методы класса:
render(data?: Partial<T>): HTMLElement - Главный метод класса. Он принимает данные, которые необходимо отобразить в интерфейсе, записывает эти данные в поля класса и возвращает ссылку на DOM-элемент. Предполагается, что в классах, которые будут наследоваться от Component будут реализованы сеттеры для полей с данными, которые будут вызываться в момент вызова render и записывать данные в необходимые DOM элементы.
setImage(element: HTMLImageElement, src: string, alt?: string): void - утилитарный метод для модификации DOM-элементов <img>
Содержит в себе базовую логику отправки запросов.
Конструктор:
constructor(baseUrl: string, options: RequestInit = {}) - В конструктор передается базовый адрес сервера и опциональный объект с заголовками запросов.
Поля класса:
baseUrl: string - базовый адрес сервера
options: RequestInit - объект с заголовками, которые будут использованы для запросов.
Методы:
get(uri: string): Promise<object> - выполняет GET запрос на переданный в параметрах ендпоинт и возвращает промис с объектом, которым ответил сервер
post(uri: string, data: object, method: ApiPostMethods = 'POST'): Promise<object> - принимает объект с данными, которые будут переданы в JSON в теле запроса, и отправляет эти данные на ендпоинт переданный как параметр при вызове метода. По умолчанию выполняется POST запрос, но метод запроса может быть переопределен заданием третьего параметра при вызове.
handleResponse(response: Response): Promise<object> - защищенный метод проверяющий ответ сервера на корректность и возвращающий объект с данными полученный от сервера или отклоненный промис, в случае некорректных данных.
Брокер событий реализует паттерн "Наблюдатель", позволяющий отправлять события и подписываться на события, происходящие в системе. Класс используется для связи слоя данных и представления.
Конструктор класса не принимает параметров.
Поля класса:
_events: Map<string | RegExp, Set<Function>>) - хранит коллекцию подписок на события. Ключи коллекции - названия событий или регулярное выражение, значения - коллекция функций обработчиков, которые будут вызваны при срабатывании события.
Методы класса:
on<T extends object>(event: EventName, callback: (data: T) => void): void - подписка на событие, принимает название события и функцию обработчик.
emit<T extends object>(event: string, data?: T): void - инициализация события. При вызове события в метод передается название события и объект с данными, который будет использован как аргумент для вызова обработчика.
trigger<T extends object>(event: string, context?: Partial<T>): (data: T) => void - возвращает функцию, при вызове которой инициализируется требуемое в параметрах событие с передачей в него данных из второго параметра.
Интерфейс описывает товар, отображаемый в каталоге интернет-магазина.
id: string — уникальный идентификатор товара;
title: string — название товара;
description: string — описание товара;
image: string — ссылка на изображение товара;
category: string — категория товара;
price: number | null — цена товара. Может быть null, если товар недоступен для покупки.
interface IProduct {
id: string;
description: string;
image: string;
title: string;
category: string;
price: number | null;
}Тип описывает доступные способы оплаты заказа.
Возможные значения:
card — оплата банковской картой;
cash — оплата наличными.
type TPayment = 'card' | 'cash';Интерфейс описывает данные покупателя, которые используются при оформлении заказа.
payment: TPayment | null — выбранный способ оплаты. Может быть null, если способ оплаты ещё не выбран;
email: string — email покупателя;
phone: string — телефон покупателя;
address: string — адрес доставки.
interface IBuyer {
payment: TPayment | null;
email: string;
phone: string;
address: string;
}Интерфейс описывает ответ сервера при запросе каталога товаров.
total: number — общее количество товаров;
items: IProduct[] — массив товаров каталога.
interface ProductsResponse {
total: number;
items: IProduct[];
}Типы описывают данные, которые приложение отправляет на сервер при оформлении заказа, и данные, которые сервер возвращает в ответ. Он формируется на основе данных покупателя, выбранных товаров и общей стоимости заказа.
Тип описывает объект заказа, отправляемый на сервер.
Заказ формируется из данных покупателя, списка выбранных товаров и общей стоимости заказа. Структура объекта соответствует формату API сервера.
payment: TPayment — выбранный способ оплаты;
email: string — email покупателя;
phone: string — телефон покупателя;
address: string — адрес доставки;
items: string[] — массив идентификаторов выбранных товаров;
total: number — общая стоимость заказа.
export type OrderRequest = {
payment: TPayment;
email: string;
phone: string;
address: string;
items: string[];
total: number;
};Тип описывает ответ сервера после успешного оформления заказа.
id: string — уникальный идентификатор оформленного заказа;
total: number — итоговая стоимость оформленного заказа.
export type OrderResponse = {
id: string;
total: number;
};Типы событий, используемые EventEmitter, объявлены в src/types/index.ts.
interface ProductsListChangedEvent {
products: IProduct[];
}interface ProductSelectionChangedEvent {
product: IProduct | null;
}interface BasketStateChangedEvent {
items: IProduct[];
total: number;
}interface BuyerDataChangedEvent {
buyer: IBuyer;
}interface FormSubmitTriggeredEvent {
form: 'order' | 'contacts';
}type OrderFieldChangedEvent =
| { field: 'payment'; value: TPayment }
| { field: 'address'; value: string };
type ContactsFieldChangedEvent =
| { field: 'email'; value: string }
| { field: 'phone'; value: string };
type FormFieldChangedEvent =
| ({ form: 'order' } & OrderFieldChangedEvent)
| ({ form: 'contacts' } & ContactsFieldChangedEvent);Класс отвечает за хранение и управление каталогом товаров, полученных с сервера. Класс не содержит логики отображения, работы с DOM или сетевых запросов.
Конструктор:
constructor(events: IEvents) — принимает экземпляр брокера событий. Начальное состояние модели инициализируется пустыми значениями.
items: IProduct[] — массив всех товаров каталога;
selectedItem: IProduct | null — товар, выбранный для подробного просмотра.
setItems(items: IProduct[]): void — сохраняет массив товаров в модель и генерирует событие изменения каталога;
getItems(): IProduct[] — возвращает массив всех товаров;
getItemById(id: string): IProduct | undefined — возвращает товар по идентификатору;
setSelectedItem(item: IProduct | null): void — сохраняет выбранный товар или сбрасывает выбор, если передан null, затем генерирует событие изменения выбранного товара;
getSelectedItem(): IProduct | null — возвращает выбранный товар.
Класс отвечает за хранение товаров, выбранных пользователем, и за расчёт агрегированных значений (общая стоимость, количество товаров, наличие товара в корзине). Класс не отвечает за отображение корзины и обработку пользовательских событий.
Конструктор:
constructor(events: IEvents) — принимает экземпляр брокера событий. Корзина инициализируется пустым массивом товаров.
items: IProduct[] — массив товаров в корзине.
getItems(): IProduct[] — возвращает массив товаров, находящихся в корзине на текущий момент;
addItem(item: IProduct): void — добавляет переданный товар в корзину, если его там ещё нет, и генерирует событие изменения корзины;
removeItem(itemId: string): void — удаляет товар из корзины по его идентификатору и генерирует событие изменения корзины;
clear(): void — очищает корзину, удаляя из неё все товары, используется после успешного оформления заказа и генерирует событие изменения корзины;
getTotalPrice(): number — возвращает общую стоимость всех товаров, находящихся в корзине.
При расчёте учитываются только товары с заданной ценой;
getItemsCount(): number — возвращает количество товаров, находящихся в корзине;
hasItem(itemId: string): boolean — проверяет наличие товара в корзине по его идентификатору. Возвращает true, если товар найден, и false — в противном случае.
Класс отвечает за хранение данных покупателя, вводимых при оформлении заказа, а также за их валидацию. Класс не выполняет отправку данных на сервер и не управляет отображением форм.
Конструктор:
constructor(events: IEvents) — принимает экземпляр брокера событий. Все поля модели инициализируются пустыми значениями или null.
payment: TPayment | null — выбранный способ оплаты. Может быть null, если способ оплаты ещё не выбран;
email: string — адрес электронной почты покупателя;
phone: string — телефон покупателя;
address: string — адрес доставки.
setData(data: Partial<IBuyer>): void — сохраняет данные покупателя в модели.
Метод принимает объект с частичным набором полей и обновляет только переданные значения, не затирая остальные данные, уже сохранённые в модели. Используется при пошаговом заполнении формы оформления заказа. После сохранения генерирует событие изменения данных покупателя;
getData(): IBuyer — возвращает текущие данные покупателя, сохранённые в модели;
clear(): void — очищает данные покупателя, сбрасывая все поля модели в начальное состояние. Используется после успешного оформления заказа и генерирует событие изменения данных покупателя;
validate(): Partial<Record<keyof IBuyer, string>> — выполняет валидацию данных покупателя в соответствии с функциональными требованиями приложения.
Метод возвращает объект с ошибками валидации, где:
ключ — имя поля;
значение — текст ошибки, связанный с проверкой этого поля.
Если поле прошло проверку, соответствующее свойство отсутствует в возвращаемом объекте. Пустой объект означает, что все данные валидны и оформление заказа может быть продолжено.
Текущие сообщения валидации:
payment — Выберите способ оплаты;
email — Укажите email;
phone — Укажите телефон;
address — Необходимо указать адрес.
Слой коммуникации отвечает за взаимодействие приложения с сервером. Он используется для получения данных с сервера и отправки данных на сервер и представляет собой изолированный слой, не связанный напрямую с моделями данных или представлением.
Данный слой реализован в виде отдельного класса, который использует композицию и работает с сервером через базовый класс Api, предоставленный в стартовом наборе проекта.
Слой коммуникации выполняет следующие задачи:
- получение каталога товаров с сервера;
- отправка данных оформленного заказа на сервер.
Класс слоя коммуникации не хранит состояние приложения, не изменяет модели данных напрямую и не содержит логики отображения интерфейса. Он только выполняет HTTP-запросы и возвращает результат вызывающему коду.
Класс WebLarekApi является представителем коммуникационного слоя приложения.
- выполняет GET-запрос для получения списка товаров и POST-запрос для отправки данных заказа;
- работает с сервером через методы
getиpostбазового классаApi.
Конструктор:
constructor(api: IApi) — принимает зависимость для HTTP-запросов, соответствующую интерфейсу IApi. Использование композиции позволяет изолировать логику коммуникации от конкретной реализации сетевых запросов.
getProducts(): Promise<IProduct[]> — выполняет GET-запрос на эндпоинт /product и получает объект типа ProductsResponse. Из ответа извлекается массив товаров items, который возвращается вызывающему коду. Возвращаемые данные используются для сохранения каталога товаров в модели данных;
postOrder(data: OrderRequest): Promise<OrderResponse> — выполняет POST-запрос к эндпоинту /order/ и отправляет на сервер данные оформленного заказа. В параметры метода передаются данные покупателя, выбранные товары и итоговая стоимость заказа в формате, соответствующем API сервера.
Метод возвращает ответ сервера, подтверждающий успешную обработку заказа.
Слой коммуникации:
не зависит от моделей данных;
не изменяет состояние моделей напрямую;
используется в основном скрипте приложения для получения и передачи данных.
После получения данных с сервера ответственность за их хранение и обработку передаётся моделям данных.
Компоненты представления отвечают за отображение разметки и генерацию пользовательских событий. Компоненты не содержат бизнес-логики и не работают с моделями данных напрямую.
Класс отвечает за вывод списка карточек каталога на главной странице.
Конструктор:
constructor(container: HTMLElement) — принимает контейнер каталога.
Поля класса:
container: HTMLElement — корневой DOM-элемент компонента.
Методы:
set catalog(items: HTMLElement[]) — устанавливает массив карточек каталога.
Класс отвечает за отображение счётчика корзины и кнопку открытия корзины.
Конструктор:
constructor(container: HTMLElement, events: IEvents) — принимает контейнер шапки и брокер событий.
Поля класса:
counterElement: HTMLElement — элемент счётчика;
basketButton: HTMLButtonElement — кнопка корзины.
Методы:
set counter(value: number) — обновляет значение счётчика в шапке.
Класс отвечает за контейнер модального окна, отображение контента и закрытие модального окна.
Конструктор:
constructor(container: HTMLElement, events: IEvents) — принимает контейнер модального окна и брокер событий.
Поля класса:
closeButton: HTMLButtonElement — кнопка закрытия;
contentElement: HTMLElement — контейнер контента;
isOpen: boolean — флаг открытого состояния.
Методы:
open(): void — открывает модальное окно;
close(): void — закрывает модальное окно;
set content(node: HTMLElement | null) — устанавливает контент модального окна.
Абстрактный родительский класс карточек каталога, превью и корзины.
Конструктор:
constructor(container: HTMLElement) — принимает корневой элемент карточки.
Поля класса:
titleElement, priceTextElement.
Методы:
сеттеры для управления отображением: title, priceText.
Абстрактный промежуточный класс для визуальных карточек каталога и превью.
Конструктор:
constructor(container: HTMLElement) — принимает корневой элемент карточки.
Поля класса:
categoryElement, imageElement.
Методы:
сеттеры для управления визуальной частью: category, imageSrc, imageAlt.
Отвечает за отображение карточки товара в каталоге.
Конструктор:
constructor(container: HTMLElement, actions: ICatalogCardActions) — принимает контейнер и обработчик клика по карточке.
Наследование:
CardCatalog наследуется от CardVisualBase.
Отвечает за отображение карточки товара в модальном окне просмотра.
Конструктор:
constructor(container: HTMLElement, actions: IPreviewCardActions) — принимает контейнер и обработчик кнопки действия.
Наследование:
CardPreview наследуется от CardVisualBase.
Поля класса:
descriptionElement: HTMLElement — элемент описания товара;
actionButton: HTMLButtonElement — кнопка действия (купить/удалить).
Методы:
set description(value: string) — устанавливает описание товара;
set actionDisabled(value: boolean) — управляет доступностью кнопки действия;
set actionText(value: string) — устанавливает текст кнопки действия.
Отвечает за отображение позиции товара в корзине.
Конструктор:
constructor(container: HTMLElement, actions: IBasketCardActions) — принимает контейнер и обработчик удаления товара.
Наследование:
CardBasket наследуется напрямую от CardBase.
Поля класса:
indexElement: HTMLElement — номер позиции;
removeButton: HTMLButtonElement — кнопка удаления.
Методы:
set index(value: number) — устанавливает номер позиции.
Отвечает за отображение списка товаров в корзине, итоговой стоимости и кнопки оформления.
Конструктор:
constructor(container: HTMLElement, events: IEvents) — принимает контейнер корзины и брокер событий.
Поля класса:
listElement: HTMLElement — контейнер списка;
totalElement: HTMLElement — элемент итоговой суммы;
submitButton: HTMLButtonElement — кнопка оформления.
Методы:
set items(cards: HTMLElement[]) — устанавливает элементы списка корзины;
set submitDisabled(value: boolean) — управляет доступностью кнопки оформления;
set total(value: number) — устанавливает итоговую стоимость в формате ru-RU (с разделением разрядов) и выводит значение с подписью синапсов.
Абстрактный родительский класс форм оформления заказа.
Конструктор:
constructor(container: HTMLFormElement, events: IEvents) — принимает форму и брокер событий.
Поля класса:
formElement, submitButton, errorsElement, events.
Методы:
set valid(value: boolean) — управляет доступностью кнопки отправки;
set errors(value: string[]) — устанавливает текст ошибок;
reset(): void — очищает форму и сбрасывает состояние.
Отвечает за отображение первого шага оформления заказа.
Конструктор:
constructor(container: HTMLFormElement, events: IEvents) — принимает форму и брокер событий.
Поля класса:
cardButton, cashButton, addressInput.
Методы:
set payment(value: 'card' | 'cash' | null) — подсвечивает выбранный способ оплаты;
set address(value: string) — устанавливает адрес доставки.
Отвечает за отображение второго шага оформления заказа.
Конструктор:
constructor(container: HTMLFormElement, events: IEvents) — принимает форму и брокер событий.
Поля класса:
emailInput, phoneInput.
Методы:
set email(value: string) — устанавливает email;
set phone(value: string) — устанавливает телефон.
Отвечает за отображение подтверждения успешной оплаты.
Конструктор:
constructor(container: HTMLElement, events: IEvents) — принимает контейнер и брокер событий.
Поля класса:
descriptionElement: HTMLElement — элемент описания успешного заказа;
closeButton: HTMLButtonElement — кнопка закрытия окна.
Методы:
set total(value: number) — устанавливает сумму списания в формате ru-RU (с разделением разрядов).
Ниже приведены события и пользовательские действия, используемые в приложении.
Все типы событий приложения объявлены в src/types/index.ts.
products:list-changed — изменение каталога товаров;
products:selection-changed — изменение выбранного товара для просмотра;
basket:state-changed — изменение содержимого корзины;
buyer:data-changed — изменение данных покупателя.
basket:icon-clicked — нажатие на иконку корзины;
basket:checkout-clicked — нажатие на кнопку оформления в корзине;
modal:close-triggered — закрытие модального окна по крестику, оверлею или Escape;
form:field-changed — изменение поля в форме оформления;
form:submit-triggered — отправка формы оформления;
order-success:close-clicked — закрытие окна успешного заказа.
card-catalog click — выбор карточки товара в каталоге;
card-preview action click — нажатие кнопки купить/удалить в карточке просмотра;
card-basket remove click — удаление позиции в корзине.
В проекте реализован отдельный класс Presenter, который отвечает за оркестрацию взаимодействия между Model, View и API.
Класс Presenter:
- подписывается на события моделей и представлений;
- вызывает публичные методы моделей;
- подготавливает данные для отображения;
- вызывает публичные методы компонентов представления;
- управляет переходами между шагами оформления заказа;
- выполняет загрузку каталога и отправку заказа через
WebLarekApi.
constructor(deps: PresenterDependencies) — принимает все зависимости приложения (модели, представления, API, шаблоны, брокер событий и базовый URL изображений).
handleProductsListChanged — рендер каталога;
handleProductSelectionChanged — открытие карточки просмотра;
handleBasketStateChanged — обновление счётчика корзины и содержимого корзины в модальном окне;
handleBasketCheckoutClick — открытие первого шага оформления;
handleFormFieldChanged — сохранение изменений полей в BuyerModel;
handleFormSubmitTriggered — переход между шагами оформления и запуск отправки заказа;
handleBuyerDataChanged — реактивное обновление состояния открытой формы;
handleOrderSuccessCloseClicked и handleModalCloseTriggered — закрытие модального окна и сброс UI-состояния.
При успешной отправке заказа итоговая сумма для окна успеха берётся из ответа сервера (OrderResponse.total).
Presenter не хранит бизнес-данные каталога/корзины/покупателя и не выполняет прямых манипуляций с DOM через document.querySelector, innerHTML и подобные API. Все изменения состояния данных выполняются через модели, а отображение — через публичный интерфейс компонентов View.