Урок 12: Управление формами (управляемые компоненты) в React

Мы уже прошли большой путь, изучили JSX, компоненты, state и props. Сегодня нас ждет одна из часто используемых тем в веб-разработке, это управление формами.

Представьте себе любую современную веб-страницу. Вход в личный кабинет, регистрация, поиск, добавление товара в корзину, комментарии в соцсетях, все это формы. В React есть два основных подхода к работе с ними. Использование управляемых и неуправляемых компонентов. В этом уроке мы сфокусируемся на первом, рекомендуемом способе, это управляемых компонентах.

Что такое управляемые компоненты?

Управляемый компонент, это такой компонент формы, данные которого полностью управляются состоянием (state) React. Вместо того чтобы самому искать в DOM, какое значение ввел пользователь (как это бывает в обычном HTML), мы сохраняем каждое изменение в state, а затем из этого state подставляем значение обратно в поле ввода.

Звучит немного запутанно? Давайте разберемся на простой аналогии. Представьте, что вы дирижер оркестра (React-приложение), а музыканты (элементы формы) это ваши инструменты. Вы не просто даете им ноты и надеетесь, что они сыграют правильно. Вы слышите каждый звук (изменение в поле ввода) и если он вам не нравится, вы тут же даете указание сыграть иначе (обновляете state и поле ввода). Вы полностью контролируете весь процесс. Это и есть управляемый компонент.

В техническом плане это означает, что у каждого поля формы (например, <input>) есть два ключевых атрибута:

  1. value. Его значение привязывается к переменной в state.

  2. onChange. Обработчик события, который вызывается при любом изменении значения в поле. Этот обработчик обновляет state.

Таким образом, получается замкнутый цикл данных:

  1. Пользователь набирает символ в <input>.

  2. Срабатывает событие onChange.

  3. Обработчик onChange вызывает функцию setState() для обновления состояния.

  4. Компонент перерисовывается с новым значением state.

  5. <input> получает обновленное значение из state через атрибут value.

Этот подход делает React «источником истины» для данных в форме. Все данные хранятся в state и интерфейс всегда им соответствует.

Связывание state с полями ввода (input)

Давайте перейдем от теории к практике. Самый распространенный элемент формы, это текстовое поле <input type="text">. Создадим простейший управляемый компонент.

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

function SimpleInput() {
  // 1. Создаем состояние для хранения значения input
  const [inputValue, setInputValue] = useState('');

  // 2. Обработчик для события onChange
  const handleInputChange = (event) => {
    // event.target это DOM-элемент input
    // event.target.value содержит текущее значение input
    setInputValue(event.target.value);
  };

  // 3. Обработчик для отправки формы
  const handleSubmit = (event) => {
    event.preventDefault(); // Предотвращаем перезагрузку страницы
    alert(`Введенное имя: ${inputValue}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Ваше имя:
        {/* 4. Связываем input с state: value и onChange */}
        <input
          type="text"
          value={inputValue}
          onChange={handleInputChange}
        />
      </label>
      <button type="submit">Отправить</button>
      {/* 5. Показываем текущее значение для наглядности */}
      <p>Текущее значение: {inputValue}</p>
    </form>
  );
}

export default SimpleInput;

Давайте разберем этот код по шагам, чтобы все было кристально понятно:

  1. Инициализация состояния. Мы используем хук useState, чтобы создать переменную состояния inputValue и функцию для ее обновления setInputValue. Начальное значение (пустая строка).

  2. Обработчик изменения handleInputChange. Эта функция вызывается каждый раз, когда пользователь что-то вводит в поле. Параметр event содержит информацию о событии. Нам критически важно свойство event.target.value это то, что пользователь ввел на данный момент. Мы берем это значение и немедленно обновляем наш state, вызывая setInputValue(event.target.value).

  3. Обработчик отправки формы handleSubmit. Стандартное поведение формы, отправить данные на сервер и перезагрузить страницу. В одностраничных приложениях (SPA) это нежелательно. event.preventDefault() отменяет это поведение. Далее мы можем делать с данными что угодно, например, отправить их на сервер с помощью fetch или вывести в alert.

  4. Связывание в JSX. В самом теге <input> мы устанавливаем два ключевых атрибута.

    • value={inputValue}. По React: «Значение этого поля всегда должно быть равно тому, что хранится в inputValue».

    • onChange={handleInputChange}. По React: «При любом изменении вызывай функцию handleInputChange».

  5. Отображение состояния. Для демонстрации мы выводим текущее значение inputValue в теге <p>. Это наглядно показывает, как состояние синхронизировано с полем ввода.

Попробуйте этот код в вашем проекте. Вы увидите, что как только вы начинаете печатать, текст под формой мгновенно обновляется. Это и есть простота управляемых компонентов в действии, полный контроль и синхронизация.

Работа с textarea

В обычном HTML элемент <textarea> определяет свое содержимое через дочерний текст:

html
<textarea>
  Какой-то текст внутри textarea
</textarea>

В React, для единообразия и чтобы сделать <textarea> управляемым компонентом, мы также используем атрибут value. Это гораздо удобнее, так как позволяет управлять многострочным вводом точно так же, как и обычным текстовым полем.

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

function FeedbackForm() {
  const [feedback, setFeedback] = useState('');

  const handleFeedbackChange = (event) => {
    setFeedback(event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('Отзыв отправлен:', feedback);
    // Здесь обычно отправка на сервер
    setFeedback(''); // Очистка формы после "отправки"
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Ваш отзыв:
        {/* Используем value, а не детей */}
        <textarea
          value={feedback}
          onChange={handleFeedbackChange}
          rows={5}
          cols={50}
        />
      </label>
      <br />
      <button type="submit">Отправить отзыв</button>
      <div>
        <h4>Предпросмотр вашего отзыва:</h4>
        <p>{feedback}</p>
      </div>
    </form>
  );
}

export default FeedbackForm;

Как видите, здесь нет никакой принципиальной разницы с <input type="text">. Мы создаем состояние feedback, привязываем его к <textarea> через value и обновляем с помощью onChange. Такой подход объединяет логику работы с различными полями ввода, делая код предсказуемым и простым для понимания.

Работа с выпадающим списком (select)

С элементом <select> в React история похожая. В HTML вы выбираете option по атрибуту selected:

html
<select>
  <option value="apple">Яблоко</option>
  <option value="banana" selected>Банан</option> <!-- Выбран по умолчанию -->
  <option value="cherry">Вишня</option>
</select>

В управляемом компоненте React мы указываем выбранное значение не в каждом <option>, а в родительском <select> с помощью атрибута value. Это самый декларативный и контролируемый способ.

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

function FruitSelector() {
  const [selectedFruit, setSelectedFruit] = useState('banana'); // Устанавливаем значение по умолчанию

  const handleFruitChange = (event) => {
    setSelectedFruit(event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    alert(`Вы выбрали: ${selectedFruit}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Выберите фрукт:
        {/* value управляет выбранным option */}
        <select value={selectedFruit} onChange={handleFruitChange}>
          <option value="apple">Яблоко</option>
          <option value="banana">Банан</option>
          <option value="cherry">Вишня</option>
          <option value="orange">Апельсин</option>
        </select>
      </label>
      <button type="submit">Подтвердить выбор</button>
      <p>Текущий выбор: <strong>{selectedFruit}</strong></p>
    </form>
  );
}

export default FruitSelector;

Что здесь происходит? Мы создаем состояние selectedFruit и устанавливаем ему начальное значение 'banana'. В теге <select> мы говорим: «Твое выбранное значение, это то, что лежит в selectedFruit». При изменении выбора (событие onChange) мы обновляем state. React следит за этим и автоматически делает активным тот <option>, у которого атрибут value совпадает с selectedFruit.

Этот подход также легко расширяется для работы с множественным выбором. Для этого нужно указать атрибут multiple={true} в <select>, а в value передавать массив выбранных значений.

jsx
function MultiSelect() {
  const [selectedColors, setSelectedColors] = useState(['red', 'blue']);

  const handleColorChange = (event) => {
    // Для множественного выбора event.target.selectedOptions, это коллекция выбранных option.
    // Преобразуем ее в массив значений.
    const selectedOptions = Array.from(event.target.selectedOptions).map(option => option.value);
    setSelectedColors(selectedOptions);
  };

  return (
    <div>
      <select multiple={true} value={selectedColors} onChange={handleColorChange}>
        <option value="red">Красный</option>
        <option value="green">Зеленый</option>
        <option value="blue">Синий</option>
        <option value="yellow">Желтый</option>
      </select>
      <p>Выбранные цвета: {selectedColors.join(', ')}</p>
    </div>
  );
}

Обработка нескольких полей ввода

В реальной форме полей почти всегда больше одного. Создавать отдельный обработчик для каждого поля, не самый эффективный путь. Вместо этого мы можем использовать единственный обработчик, который будет определять, какое поле изменилось и обновлять соответствующий кусочек state.

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

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

function RegistrationForm() {
  // 1. Создаем единый объект состояния для всех полей формы
  const [formData, setFormData] = useState({
    firstName: '',
    lastName: '',
    email: '',
    password: ''
  });

  // 2. Создаем один универсальный обработчик для всех полей
  const handleInputChange = (event) => {
    // Получаем имя поля и его значение
    const { name, value } = event.target;

    // Обновляем state. Используем функцию обновления для гарантированной работы с актуальным state
    setFormData(prevFormData => {
      return {
        ...prevFormData, // Копируем все предыдущие поля
        [name]: value    // Обновляем конкретное поле, используя его name
      };
    });
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    // В реальном приложении здесь был бы fetch-запрос
    console.log('Данные для регистрации:', formData);
    alert(`Регистрация прошла успешно для ${formData.firstName} ${formData.lastName}!`);
    // Очистка формы
    setFormData({
      firstName: '',
      lastName: '',
      email: '',
      password: ''
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>
          Имя:
          <input
            type="text"
            name="firstName" // Важно: атрибут name должен совпадать с ключом в state
            value={formData.firstName}
            onChange={handleInputChange}
          />
        </label>
      </div>
      <div>
        <label>
          Фамилия:
          <input
            type="text"
            name="lastName"
            value={formData.lastName}
            onChange={handleInputChange}
          />
        </label>
      </div>
      <div>
        <label>
          Email:
          <input
            type="email"
            name="email"
            value={formData.email}
            onChange={handleInputChange}
          />
        </label>
      </div>
      <div>
        <label>
          Пароль:
          <input
            type="password"
            name="password"
            value={formData.password}
            onChange={handleInputChange}
          />
        </label>
      </div>
      <button type="submit">Зарегистрироваться</button>
    </form>
  );
}

export default RegistrationForm;

Ключевые моменты этого подхода:

  1. Единый объект state. Все данные формы хранятся в одном объекте formData. Это очень удобно, так как при отправке формы нам не нужно собирать данные из разных кусочков state (они уже все вместе).

  2. Атрибут name. Каждому полю ввода мы задаем уникальный атрибут name, который точно соответствует имени свойства в нашем объекте formData (firstNamelastName и т.д.).

  3. Универсальный обработчик handleInputChange:

    • С помощью деструктуризации мы из event.target извлекаем name (имя поля) и value (его значение).

    • Далее мы обновляем state. Мы используем функцию обновления для setFormData, которая принимает предыдущее состояние (prevFormData). Это лучшая практика, особенно если обновления state могут быть асинхронными.

    • Мы возвращаем новый объект. Сначала оператором (...prevFormData) копируем все старые поля, а затем перезаписываем одно конкретное поле [name]: value. Квадратные скобки вокруг [name] это и есть вычисляемое свойство, которое позволяет использовать значение переменной name как ключ объекта.

Этот паттерн невероятно мощен и масштабируем. Вы можете добавить в форму десятки новых полей и вам не придется создавать для них новые обработчики, handleInputChange справится со всеми.

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

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

Задача 1: Поиск с предпросмотром

Создайте компонент поиска. Пользователь вводит запрос в поле и ниже мгновенно отображается результат поиска (пока что просто текст: «Вы ищете: [запрос]»). Добавьте кнопку «Найти», при нажатии на которую выводится alert с итоговым запросом.

Подсказки:

  • Используйте управляемый компонент для input.

  • Для предпросмотра просто выводите значение из state в теге <p> или <div>.

  • Не забудьте event.preventDefault() в обработчике кнопки.

Задача 2: Форма обратной связи с селектом и текстовой областью

Создайте форму обратной связи, которая включает:

  • Выпадающий список (<select>) для выбора темы обращения (например, «Техническая проблема», «Вопрос по оплате», «Общее пожелание»).

  • Текстовую область (<textarea>) для самого сообщения.

  • Поле для ввода email.

  • Кнопку отправки.

При отправке формы выводите в консоль объект со всеми собранными данными.

Подсказки:

  • Используйте паттерн с единым объектом state и универсальным обработчиком, как в примере с формой регистрации.

  • Для <select> не забудьте указать атрибут name и привязать его value к state.

Задача 3 (продвинутая): Простая валидация формы

Расширьте форму регистрации из нашего примера. Добавьте простую валидацию:

  • Поля «Имя» и «Фамилия» не должны быть пустыми.

  • Email должен содержать символ @.

  • Пароль должен быть не короче 6 символов.

Выводите сообщения об ошибках под соответствующими полями, но только после того, как пользователь попытался отправить форму (или после того, как он покинул поле, это называется «onBlur»). Кнопка «Зарегистрироваться» должна быть неактивной (атрибут disabled), пока есть ошибки валидации.

Подсказки:

  • Создайте отдельный state для ошибок, например, const [errors, setErrors] = useState({}), где ключи это имена полей, а значения строки с ошибками.

  • В обработчике handleSubmit перед отправкой проверяйте данные и заполняйте объект errors.

  • В JSX под каждым полем выводите errors.fieldName, если ошибка для этого поля существует.

  • Условие для атрибута disabled кнопки может быть таким: disabled={Object.keys(errors).length > 0}.

На сегодня все! Мы подробно разобрали, что такое управляемые компоненты и как с их помощью создавать мощные, предсказуемые и удобные формы в React. Вы научились работать с текстовыми полями, textarea, select и эффективно обрабатывать формы с множеством полей.

Как всегда, если есть вопросы, пишите в комментариях.

Хотите освоить React полностью? Переходи к полному курсу с уроками по React для начинающих, где мы с нуля разбираем все тонкости этой мощной библиотеки, пишем реальные проекты и закрепляем знания на практике.

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

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

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