Видеоуроки, интерактивный редактор и сохранение прогресса — бесплатно, сразу после входа.
ВойтиСоздать аккаунт — бесплатноЗакончили урок?
Войдите, чтобы отмечать прогресс
В этом решении мы создаём интерактивный чат с автоматической прокруткой, индикатором печатания и имитацией ответов бота.
Мы создаём константу с начальными сообщениями вне компонента. Каждое сообщение - это объект с уникальным id, текстом, отправителем ('user' или 'bot') и временем. Эти данные используются как начальное значение состояния, чтобы чат не был пустым при загрузке.
useRef создаёт объект с полем current, которое может хранить любое значение. Начальное значение null, но позже React присвоит ему ссылку на DOM элемент. В отличие от состояния, изменение ref не вызывает перерисовку компонента. Ref сохраняется между рендерами - это как "коробка", в которую можно положить что угодно.
Эффект выполняется после каждого рендера, когда изменяется массив messages. При добавлении нового сообщения массив меняется, запускается эффект, и вызывается scrollToBottom(). Это автоматически прокручивает окно чата вниз, чтобы пользователь видел последнее сообщение.
Метод scrollIntoView() прокручивает страницу так, чтобы элемент был виден. Опция behavior: 'smooth' создаёт плавную анимацию вместо резкого скачка. Оператор ?. (optional chaining) проверяет, что current не null перед вызовом метода. При первом рендере current может быть null, поэтому без ?. была бы ошибка.
Пустой div размещается в самом конце списка сообщений. React автоматически присваивает ссылку на этот элемент в messagesEndRef.current. Когда мы вызываем scrollIntoView(), страница прокручивается именно к этому невидимому div - то есть в самый низ чата.
Метод trim() удаляет пробелы с начала и конца строки. Если после этого строка пустая, функция завершается с помощью return. Это предотвращает отправку сообщений из одних пробелов или пустых сообщений.
Мы создаём объект сообщения. Date.now() даёт уникальный id - текущее время в миллисекундах. sender: 'user' помечает, что это сообщение от пользователя. toLocaleTimeString() форматирует текущее время в формат "14:35" - опции указывают локаль (русская) и формат (только часы и минуты с двумя цифрами).
Spread оператор ...messages разворачивает все существующие сообщения, а newMessage добавляется в конец. Результат - новый массив со всеми старыми сообщениями плюс новое. Затем мы очищаем поле ввода, устанавливая пустую строку. Пользователь сразу может писать следующее сообщение.
Сразу устанавливаем флаг isTyping в true, чтобы показать индикатор "печатает...". setTimeout откладывает выполнение кода на 1500 миллисекунд (1.5 секунды). Это создаёт иллюзию, что бот думает и набирает ответ. После добавления ответа сбрасываем флаг в false.
Массив botResponses содержит готовые фразы. Math.random() возвращает случайное число от 0 до 1. Умножаем на длину массива, получаем число от 0 до 7 (для 7 элементов). Math.floor() округляет вниз до целого - получаем случайный индекс. Это делает бота менее предсказуемым.
Вместо setMessages([...messages, botMessage]) мы используем функциональную форму с параметром prev. Это важно внутри setTimeout - к моменту выполнения callback значение переменной messages может устареть (если пользователь отправил ещё сообщения). Функциональная форма гарантирует работу с самым актуальным состоянием.
Базовый класс message всегда присутствует. Дополнительный класс зависит от отправителя - sent для пользователя, received для бота. CSS использует эти классы для разного выравнивания: сообщения пользователя справа, бота слева. Это стандартный паттерн визуализации чатов.
Простой тернарный оператор выбирает эмодзи в зависимости от отправителя. Человечек для пользователя, робот для бота. Это визуально разделяет сообщения и делает интерфейс более понятным.
Индикатор показывается только когда isTyping === true. Три точки и текст "печатает..." создают знакомый эффект - как в реальных мессенджерах. CSS анимирует точки, создавая эффект набора текста. Это даёт визуальную обратную связь, что бот обрабатывает сообщение.
Метод preventDefault() отменяет стандартное поведение формы - перезагрузку страницы. Без него при нажатии Enter или клике на кнопку страница перезагрузилась бы, и все сообщения в состоянии потерялись. Это первое, что нужно сделать в обработчике submit формы.
Событие onSubmit на <form> (а не onClick на кнопке) захватывает все способы отправки - клик на кнопку, нажатие Enter в поле ввода. Это правильный способ работы с формами в React.
Кнопка отключена, когда поле пустое или содержит только пробелы. trim() убирает пробелы, и если результат пустая строка, disabled становится true. Отключенная кнопка серая и некликабельная - визуальная подсказка, что нечего отправлять.
Если массив сообщений пустой (хотя у нас есть начальные сообщения), показываем приглашение начать общение. Это улучшает пользовательский опыт - вместо пустого белого экрана пользователь видит дружелюбное сообщение. Тернарный оператор выбирает между заглушкой и списком сообщений.