Видеоуроки, интерактивный редактор и сохранение прогресса — бесплатно, сразу после входа.
ВойтиСоздать аккаунт — бесплатноЗакончили урок?
Войдите, чтобы отмечать прогресс
В этом решении мы разбиваем приложение чата на независимые компоненты, каждый из которых отвечает за свою часть интерфейса.
ChatHeader выделен в отдельный компонент, потому что шапка - это независимая часть интерфейса с фиксированной позицией вверху. Пока компонент не принимает props и отображает захардкоженные данные, но в будущем имя собеседника и статус можно передавать через параметры для переиспользования с разными чатами.
Message инкапсулирует логику отображения одного сообщения. Мы вынесли его отдельно, потому что сообщения имеют разное поведение - свои сообщения справа без имени, чужие слева с именем отправителя. Условный класс message.isOwn ? 'own-message' : 'other-message' применяет разные стили. Условный рендеринг {!message.isOwn && <span>...} показывает имя только для чужих сообщений.
Messages - это контейнер для списка сообщений. Мы выделили его отдельно, чтобы изолировать логику отображения списка от логики одного сообщения. Компонент просто проходит по массиву и создаёт Message для каждого элемента. В будущем здесь может появиться автопрокрутка вниз, группировка сообщений по датам или индикатор загрузки истории.
MessageInput объединяет поле ввода и кнопку отправки. Мы вынесли форму в компонент, потому что она имеет собственную логику - обработку Enter для отправки. Функция handleKeyDown живёт внутри компонента, потому что это деталь реализации формы. Компонент получает через props функцию onSend и вызывает её при Enter или клике на кнопку.
Обработчик проверяет, что нажата клавиша Enter (e.key === 'Enter'), и вызывает функцию отправки. Это улучшает UX - пользователи привыкли отправлять сообщения Enter. Логика находится в MessageInput, а не в App, потому что это поведение специфично для формы, а не для всего приложения.
В поле ввода мы извлекаем текст из события e.target.value и передаём в функцию onChange, которая пришла через props. Родительский компонент получает чистое значение, а не объект события. Это упрощает использование - в App мы просто передаём setNewMessage, которая принимает строку.
Вся бизнес-логика отправки остаётся в App. Проверка newMessage.trim() не позволяет отправить пустое сообщение. Date.now() создаёт текущее время, toLocaleTimeString форматирует его в "10:35". Мы создаём объект сообщения с уникальным id, текстом, временем и флагом isOwn: true. Сообщение добавляется в конец массива через [...messages, message], а поле ввода очищается.
Метод toLocaleTimeString форматирует время в читаемый вид. Первый параметр 'ru-RU' устанавливает русскую локаль. Опции { hour: '2-digit', minute: '2-digit' } указывают, что нужны только часы и минуты с двумя цифрами, без секунд. Результат выглядит как "14:35".
Компонент App собирает три части чата вместе. Структура максимально простая - шапка, сообщения, форма ввода. Всё состояние (messages, newMessage) и логика (handleSendMessage) живут здесь, а визуальные компоненты только отображают данные и вызывают callback-функции. ChatHeader автономен, Messages получает массив, MessageInput управляет вводом.
Мы передаём setNewMessage напрямую в prop onChange. Это работает, потому что MessageInput вызывает onChange(e.target.value) - передаёт строку, а setNewMessage принимает строку. Не нужна обёртка (value) => setNewMessage(value), сигнатуры совпадают.
Spread оператор ...messages разворачивает все существующие сообщения, а новое сообщение добавляется в конец массива. Это создаёт новый массив, не мутируя оригинал - правило React. Сообщения отображаются в порядке добавления, новые внизу, как в реальных чатах.