Урок 8: События в React

Тема восьмого урока, это события в React. Мы научим наши компоненты «слышать» клики, ввод текста, отправку форм и множество других действий. Именно события превращают статичный сайт в живое, интерактивное веб-приложение. Представьте кнопку, которая ничего не делает или поле ввода, в которое нельзя написать… Звучит скучно, правда? Давайте оживлять.

Обработка событий: onClick, onChange, onSubmit

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

Во-первых, в React имена событий пишутся в camelCase, а не в нижнем регистре. onclick становится onClickonsubmit — onSubmit.

Во-вторых и это самое главное, в качестве обработчика мы передаем не строку, а функцию. В HTML мы бы написали onclick="handleClick()", а в React мы пишем onClick={handleClick}. Обратите внимание: мы передаем ссылку на функцию, а не вызываем ее (не ставим скобки). Если мы напишем onClick={handleClick()}, то функция выполнится сразу же, в момент рендера компонента, а не в момент клика.

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

onClick (реакция на клик)

Событие onClick это, без преувеличения, хлеб и масло интерактивности. Оно вешается на любые кликабельные элементы: кнопки, ссылки, дивы, картинки и т.д.

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

jsx
import React, { useState } from 'react';

function Counter() {
  // Состояние для хранения текущего значения счетчика
  const [count, setCount] = useState(0);

  // Обработчик события для клика
  const handleIncrement = () => {
    setCount(count + 1);
    console.log('Счетчик увеличен!');
  };

  // Обработчик для сброса
  const handleReset = () => {
    setCount(0);
  };

  return (
    <div>
      <p>Вы кликнули {count} раз(а)</p>
      {/* Передаем функции handleIncrement и handleReset как обработчики */}
      <button onClick={handleIncrement}>Кликни меня!</button>
      <button onClick={handleReset}>Сбросить</button>
    </div>
  );
}

export default Counter;

Что здесь происходит? Мы создали две функции-обработчика, handleIncrement и handleReset. Когда пользователь кликает на первую кнопку, React вызывает функцию handleIncrement, которая, в свою очередь, обновляет состояние count через setCount. Это приводит к повторному рендеру компонента и мы видим на экране новое значение.

Очень важно понимать поток данных: событие (клик) -> вызов обработчика -> обновление состояния -> повторный рендер.

А что, если нам нужно передать в обработчик какой-то параметр? Например, идентификатор элемента. Мы не можем написать onClick={handleDelete(item.id)}, потому что это вызовет функцию сразу. Здесь нам на помощь приходят стрелочные функции.

jsx
function ToDoList() {
  const [tasks, setTasks] = useState(['Выучить React', 'Написать приложение', 'Выпить кофе']);

  const handleDelete = (taskIndex) => {
    const newTasks = tasks.filter((_, index) => index !== taskIndex);
    setTasks(newTasks);
  };

  return (
    <ul>
      {tasks.map((task, index) => (
        <li key={index}>
          {task}
          {/* Используем стрелочную функцию для передачи параметра */}
          <button onClick={() => handleDelete(index)}>Удалить</button>
        </li>
      ))}
    </ul>
  );
}

Здесь () => handleDelete(index) создает новую функцию «на лету», которая будет вызвана только при клике и именно эта функция уже вызовет handleDelete с нужным index.

onChange  (отслеживаем ввод данных)

Событие onChange это верный спутник всех элементов форм, которые подразумевают ввод или выбор данных: <input><textarea><select>. Оно срабатывает при каждом изменении значения в поле.

Самый классический пример, это поле ввода для поиска или форма входа.

jsx
import React, { useState } from 'react';

function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  // Обработчик для поля email
  const handleEmailChange = (event) => {
    setEmail(event.target.value);
  };

  // Обработчик для поля password
  const handlePasswordChange = (event) => {
    setPassword(event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault(); // Предотвращаем перезагрузку страницы (об этом ниже!)
    console.log('Email:', email, 'Password:', password);
    // Здесь обычно отправка данных на сервер
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Email:</label>
        {/* value и onChange обязательная пара для контролируемых компонентов */}
        <input
          type="email"
          value={email}
          onChange={handleEmailChange}
        />
      </div>
      <div>
        <label>Пароль:</label>
        <input
          type="password"
          value={password}
          onChange={handlePasswordChange}
        />
      </div>
      <button type="submit">Войти</button>
    </form>
  );
}

export default LoginForm;

Обрати внимание на ключевой момент: мы связали значение поля (value={email}) с нашим состоянием и обработчик onChange, который это состояние обновляет. Такой компонент называется «контролируемым» (controlled component). React становится единственным источником истины для значения этого поля. Это рекомендуемый подход в React, так как он дает нам полный контроль над данными.

Объект event это синтетическое событие React, которое является оберткой над нативным браузерным событием. Оно имеет те же свойства и методы, но работает одинаково во всех браузерах. Самый часто используемый event.target, который указывает на DOM-элемент, вызвавший событие. А event.target.value содержит текущее значение этого элемента.

onSubmit (обрабатываем отправку формы)

Когда пользователь заполнил форму и нажал Enter или кнопку «Отправить», происходит событие onSubmit. Обработка этого события идеальное место для того, чтобы проверить все данные формы (валидация) и отправить их на сервер.

В нашем примере с формой входа мы уже использовали onSubmit. Обрати внимание, что мы повесили его на тег <form>, а не на кнопку. Это более правильный и семантичный подход, так как форма может быть отправлена не только кликом по кнопке, но и нажатием Enter в любом из ее полей.

Отмена стандартного поведения с preventDefault()

А теперь давайте поговорим о том, как «усмирить» стандартное поведение браузера. У многих событий в вебе есть действия по умолчанию. Например:

  • При клике по ссылке (<a>) браузер переходит по указанному адресу.

  • При отправке формы (<form>) браузер пытается отправить данные на URL из атрибута action и перезагружает страницу.

В традиционном JavaScript мы бы использовали event.preventDefault() для отмены этого поведения. В React все точно так же, но event, это наш синтетический.

Вернемся к нашему примеру с формой входа. Если бы мы не вызвали event.preventDefault() в функции handleSubmit, браузер попытался бы отправить данные формы, перезагрузив при этом страницу. А мы ведь строим одностраничное приложение (SPA), где перезагрузки страницы это то, чего мы всячески стараемся избегать! Нам нужно перехватить управление, обработать данные самостоятельно (например, отправив их на сервер с помощью fetch или Axios) и обновить интерфейс без перезагрузки.

Давай рассмотрим еще один яркий пример с ссылкой.

jsx
function Navigation() {
  const handleLinkClick = (event) => {
    // Отменяем стандартное поведение браузера (переход по ссылке)
    event.preventDefault();
    // Теперь мы можем выполнить свою логику, например,
    // изменить состояние или осуществить навигацию с помощью React Router (это тема будущих уроков!)
    console.log('Навигация обработана через React!');
  };

  return (
    <nav>
      {/* Эта ссылка никуда не приведет физически, пока мы не решим, что делать внутри handleLinkClick */}
      <a href="https://example.com" onClick={handleLinkClick}>
        Нажми на меня (ничего не произойдет в стандартном смысле)
      </a>
    </nav>
  );
}

Запомни: почти всегда при обработке события onSubmit формы тебе нужно будет вызывать event.preventDefault(). Для ссылок это не всегда обязательно, но становится критически важным, когда ты используешь клиентскую маршрутизацию (например, с React Router).

Практические задачи для закрепления

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

Задача 1: Светофор

Создай компонент TrafficLight. У него должно быть три состояния: «красный», «желтый», «зеленый». При клике на кнопку «Дальше» цвет должен меняться по классическому циклу: красный -> желтый -> зеленый -> желтый -> красный и так далее. Отображай текущий цвет в виде окрашенного круга и текста.

Решение (набросок):

jsx
import React, { useState } from 'react';

function TrafficLight() {
  const lights = ['red', 'yellow', 'green'];
  const [currentIndex, setCurrentIndex] = useState(0);

  const handleNext = () => {
    // Логика циклического переключения индекса
    setCurrentIndex((prevIndex) => (prevIndex + 1) % lights.length);
    // Но по правилам: красный -> зеленый -> желтый -> красный.
    // Тебе нужно будет реализовать более сложную логику с использованием условий или другого массива последовательностей.
  };

  return (
    <div>
      <div style={{
        width: '100px',
        height: '100px',
        borderRadius: '50%',
        backgroundColor: lights[currentIndex]
      }}></div>
      <p>Текущий сигнал: {lights[currentIndex]}</p>
      <button onClick={handleNext}>Дальше</button>
    </div>
  );
}

Задание: допиши правильную логику переключения!

Задача 2: Список покупок

Создай компонент ShoppingList. Пользователь должен иметь возможность:

  1. Вводить название товара в input.

  2. Добавлять его в список по нажатию кнопки «Добавить» или клавиши Enter.

  3. Удалять товар из списка по клику на него (или на кнопку «Удалить» рядом с ним).

Решение (ключевые моменты):

jsx
function ShoppingList() {
  const [items, setItems] = useState([]);
  const [inputValue, setInputValue] = useState('');

  const handleAddItem = (event) => {
    event.preventDefault(); // Если форма, предотвращаем отправку
    if (inputValue.trim()) {
      setItems([...items, inputValue]);
      setInputValue(''); // Очищаем input после добавления
    }
  };

  const handleDeleteItem = (indexToDelete) => {
    setItems(items.filter((_, index) => index !== indexToDelete));
  };

  return (
    <div>
      <form onSubmit={handleAddItem}>
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="Введите товар..."
        />
        <button type="submit">Добавить</button>
      </form>
      <ul>
        {items.map((item, index) => (
          <li key={index} onClick={() => handleDeleteItem(index)}>
            {item}
          </li>
        ))}
      </ul>
    </div>
  );
}

Задача 3: Простой рисовальщик

Создай компонент SimpleDraw. Это должно быть поле (например, div с границей). При движении зажатой мышью внутри этого поля должны появляться точки (или маленькие div), создавая эффект рисования. Подсказка: тебе понадобятся события onMouseDown (начали зажатие), onMouseMove (двигаем мышью) и onMouseUp (отпустили кнопку).

Решение (концепт):

jsx
function SimpleDraw() {
  const [isDrawing, setIsDrawing] = useState(false);
  const [dots, setDots] = useState([]);

  const handleMouseDown = (event) => {
    setIsDrawing(true);
    // Добавляем первую точку
    const newDot = { x: event.clientX, y: event.clientY };
    setDots(prevDots => [...prevDots, newDot]);
  };

  const handleMouseMove = (event) => {
    if (!isDrawing) return; // Рисуем только если зажата кнопка
    const newDot = { x: event.clientX, y: event.clientY };
    setDots(prevDots => [...prevDots, newDot]);
  };

  const handleMouseUp = () => {
    setIsDrawing(false);
  };

  return (
    <div
      style={{ width: '500px', height: '500px', border: '1px solid black', position: 'relative' }}
      onMouseDown={handleMouseDown}
      onMouseMove={handleMouseMove}
      onMouseUp={handleMouseUp}
    >
      {dots.map((dot, index) => (
        <div
          key={index}
          style={{
            position: 'absolute',
            left: dot.x - 2 + 'px', // Центрируем точку
            top: dot.y - 2 + 'px',
            width: '4px',
            height: '4px',
            backgroundColor: 'black',
            borderRadius: '50%'
          }}
        ></div>
      ))}
    </div>
  );
}

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

← К оглавлению курса

Поделиться статьей:
Поддержать автора блога

Поддержка автора осуществляется с помощью специальной формы ниже, предоставленной сервисом «ЮMoney». Все платёжные операции выполняются на защищённой странице сервиса, что обеспечивает их корректность и полную безопасность.

Персональные рекомендации
Оставить комментарий