2.10 Формы

Работа элементов HTML-форм в React немного отличается от работы других DOM-элементов, потому что элементы форм по своей природе обладают некоторым внутренним состоянием. К примеру, данная форма в нативном HTML принимает только имя:

Код
        
  <form>
    <label>
      Name: <input type="text" name="name" />
    </label>
    <input type="submit" value="Submit" />
  </form>
    

Эта форма имеет поведение HTML-формы по умолчанию: просмотр новой страницы, когда пользователь посылает форму. Если вам необходимо это поведение в React, оно работает как обычно. Но в большинстве случаев удобно иметь JavaScript-функцию, которая обрабатывает отправку формы и имеет доступ к данным, которые пользователь ввел в форму. Стандартным способом достижения этой цели является использование подхода «контролируемые компоненты».


2.10.1 Контролируемые компоненты

В HTML-элементы форм, такие как <input>, <textarea> и <select> как правило хранят свое собственное состояние и обновляют его на основании пользовательского ввода. В React модифицируемое состояние как правило является собственностью компонентов и обновляется только с помощью setState().

Мы можем скомбинировать эти обе особенности, делая состояние React “единственным источником достоверной информации (истины)”. В свою очередь React-компонент, который отрисовывает форму, также контролирует, что происходит на этой форме в ответ на последующий ввод пользователя. Элемент ввода формы (например, input), значение которого контролируется React, в этом случае называется «контролируемый компонент».

К примеру, если мы хотим в предыдущем примере делать лог имени, когда форма отправляется, мы можем написать форму как контролируемый компонент:

Код
        
  class LoginForm extends React.Component {
    constructor(props) {
      super(props);
      this.state = {login: '', password: ''};

      this.onLoginChange = this.onLoginChange.bind(this);
      this.onPasswordChange = this.onPasswordChange.bind(this);
      this.onSubmit = this.onSubmit.bind(this);
    }

    onSubmit(event){
      alert(`${this.state.login}, добро пожаловать!`);
      event.preventDefault();
    }

    onPasswordChange(event){
      this.setState({password: event.target.value});
    }

    onLoginChange(event) {
      this.setState({login: event.target.value});
    }

    render() {
      return (
        <form onSubmit={this.onSubmit}>
          <p><label> Логин: <input type="text" name="login" value={this.state.login}
                           onChange={this.onLoginChange}/></label></p>
          <p><label> Пароль: <input type="password" name="password" value={this.state.password}
                            onChange={this.onPasswordChange}/></label></p>
          <p><input type="submit" value="Submit" /></p>
        </form>
      );
    }
  }

  ReactDOM.render(<LoginForm />,  document.getElementById('root'));
    

Посмотреть в CodePen

Как только атрибут value устанавливается на нашем элементе формы c name="login", отображаемое значение будет всегда равно this.state.login, делая состояние React источником достоверной информации. Так как onLoginChange выполняется на каждое нажатие клавиши, чтобы обновить состояние компонента, отображаемое значение будет обновляться с каждым нажатием, когда пользователь печатает.

В контролируемом компоненте, любая модификация состояния будет иметь ассоциированную функцию-обработчик. Это делает его простым для изменения или проверки данных, вводимых пользователем. К примеру, если мы хотим, чтобы логин вводился только в верхнем регистре, мы можем написать onLoginChange как:

Код
        
  onLoginChange(event) {
     this.setState({login: event.target.value.toUpperCase()});
  }
    

2.10.2 Тег textarea

В HTML-элемент <textarea> определяет введенный в него текст по его потомкам:

Код
        
  <textarea>
    Дорогие посетители сайта! Желаем вам приятного изучения ReactJS.
  </textarea>
    

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

Код
        
  class MessageForm extends React.Component {
    constructor(props) {
      super(props);
      this.state = {email: '', message: 'Текст сообщения'};

      this.onEmailChange = this.onEmailChange.bind(this);
      this.onMessageChange = this.onMessageChange.bind(this);
      this.onSubmit = this.onSubmit.bind(this);
    }

    onSubmit(event){
      alert(`Сообщение успешно отправлено получателю "${this.state.email}"`);
      event.preventDefault();
    }

    onMessageChange(event){
      this.setState({message: event.target.value});
    }

    onEmailChange(e) {
      this.setState({email: e.target.value});
    }

    render() {
      return (
        <form onSubmit={this.onSubmit}>
          <p><label> email получателя: <input type="text" name="email" value={this.state.email}
                           onChange={this.onEmailChange}/></label></p>
          <p><label>Текст сообщения: <textarea type="text" name="message" value={this.state.message}
            onChange={this.onMessageChange}/></label></p>
          <p><input type="submit" value="Submit" /></p>
        </form>
      );
    }
  }

  ReactDOM.render(<MessageForm />,  document.getElementById('root'));
    

Посмотреть в CodePen

Обратите внимание, что this.state.value инициализируется в конструкторе, поэтому textarea показывается с некоторым текстом в нем.


2.10.3 Тег select

В HTML тег <select> создает выпадающий список. К примеру данный HTML создает выпадающий список языков программирования:

Код
        
  <select>
    <option value="C++">C++</option>
    <option value="Java">Java</option>
    <option value="C#">C#</option>
    <option selected value="JavaScript">JavaScript</option>
    <option value="Scala">Scala</option>
  </select>
    

Обратите внимание, что первоначально выбрана опция “JavaScript”, потому что задан атрибут selected. React вместо использования атрибута selected, использует атрибут value на корневом теге select. Это удобнее в контролируемом компоненте, потому что вам необходимо обновлять его только в одном месте. Например:

Код
        
  class LanguageForm extends React.Component {
    constructor(props) {
      super(props);
      this.state = {language: 'JavaScript'};

      this.onSelectChange = this.onSelectChange.bind(this);
      this.onSubmit = this.onSubmit.bind(this);
    }

    onSelectChange(event) {
      this.setState({language: event.target.value});
    }

    onSubmit(event) {
      alert(`Вы выбрали язык: ${this.state.language}`);
      event.preventDefault();
    }

    render() {
      return (
        <form onSubmit={this.onSubmit}>
          <label>
            Выберите язык программирования:
            <select value={this.state.language} onChange={this.onSelectChange}>
              <option value="C++">C++</option>
              <option value="Java">Java</option>
              <option value="C#">C#</option>
              <option value="JavaScript">JavaScript</option>
              <option value="Scala">Scala</option>
            </select>
          </label>
          <input type="submit" value="Submit" />
        </form>
      );
    }
  }

  ReactDOM.render(<LanguageForm />,  document.getElementById('root'));
    

Посмотреть в CodePen

В целом, это делает работу тегов <input type="text">, <textarea> и <select> очень похожей – они все принимают атрибут value, который вы можете использовать, чтобы реализовать контролируемый компонент.

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

Код
        
  <select multiple={true} value={['B', 'C']}>
    

2.10.4 Тег input типа file

В HTML тег <input type="file"/> позволяет пользователю выбирать один или несколько файлов из хранилища своего устройства для загрузки на сервер или манипулировать собой с помощью JavaScript через File API.

Код
        
  <input type="file" />
    

Поскольку его значение доступно только для чтения, это неконтролируемый компонент в React. Он обсуждается вместе с другими неконтролируемыми компонентами позже в документации.


2.10.5 Обработка множества input

Когда вам необходимо обрабатывать множество контролируемых элементов input, вы можете добавить атрибут name на каждый элемент и позволить функции-обработчику выбрать, что делать на основании значения event.target.name.

Например:

Код
        
  class PersonForm extends React.Component {
    constructor(props) {
      super(props);
      this.state = {sex: 'female', firstName: '', lastname: '', email: '', phone: ''};
      this.onInputChange = this.onInputChange.bind(this);
    }

    onInputChange(event) {
      const name = event.target.name;
      this.setState({[name]: value});
    }

    render() {
      return (
        <form>
          <label>First Name: <input name="firstName"  type="text"
                               value={this.state.firstName} onChange={this.onInputChange}/></label>
          <label> Last Name: <input name="lastName"  type="text"
                               value={this.state.lastName} onChange={this.onInputChange}/></label>
          <label> Email: <input name="email"  type="email"
                               value={this.state.email} onChange={this.onInputChange}/></label>
          <label> Phone: <input name="phone"  type="tel"
                               value={this.state.phone} onChange={this.onInputChange}/></label>
          <label> Sex: <select name="sex"  value={this.state.sex} onChange={this.onInputChange}>
              <option value="male">Male</option>
              <option value="female">Female</option>
            </select>
          </label>
        </form>
      );
    }
  }

  ReactDOM.render(<PersonForm />,  document.getElementById('root'));
    

Посмотреть в CodePen

Обратите внимание, как мы использовали ES6 синтаксис вычисляемого имени свойства, чтобы обновить ключ состояния в соответствии с данным именем тега input:

Код
        
  this.setState({
    [name]: value
  });
    

Это эквивалент данного ES5 кода:

Код
        
  var partialState = {};
  partialState[name] = value;
  this.setState(partialState);
    

Также, так как setState() автоматически мерджит частичное состояние в текущее состояние, нам лишь необходимо вызывать его с изменившейся частью.


2.10.5 Пустое значение атрибута value контролируемого input

Указание значения этого атрибута на контролируемом компоненте предотвращает изменение пользователем значений полей формы до тех пор, пока вы не разрешите. Если вы указали value, но input все еще редактируемый, вы могли случайно установить value в undefined или null.

Следующий код демонстрирует это. (Сначала input заблокирован, но становится редактируемым после короткой задержки.)

Код
        
  ReactDOM.render(<input value="hi" />, mountNode);

  setTimeout(function() {
    ReactDOM.render(<input value={null} />, mountNode);
  }, 1000);
    

2.10.6 Альтернативы контролируемым компонентам

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