Формы (Forms)

Элемэнты HTML-формы працуюць трохі інакш, чым іншыя элемэнты DOM у React, таму што элемэнты формы па прыродзе валодаюць некаторым унутраным станам (state). Напрыклад, гэтая форма ў звычайным HTML прымае адно імя:

<form>
  <label>
    Name/Імя:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="Submit" />
</form>

Гэтая форма мае паводзіны HTML-формы па змаўчаньні — прагляданьне на новай старонцы, калі карыстальнік зацьвярджае форму. Калі вы хочаце такіх паводзінаў у React, то гэта працуе проста. Але ў большасьці выпадкаў зручна мець функцыю JavaScript, якая апрацоўвае перадачу формы ды мае доступ да даных, якія карыстальнік увёў у форму. Стандартным спосабам дасягненьня гэтага зьяўляецца выкарыстаньне мэтаду, які называецца “кантралюемыя кампанэнты”.

Кантралюемыя кампанэнты (Controlled Components)

У HTML, элемэнты формы, такія як <input>, <textarea> і <select>, як правіла, падтрымліваюць свой уласны стан (state) ды абнаўляюць яго на аснове ўводу карыстальніка (user input). У React зьменлівы стан (mutable state) звычайна захоўваецца ўва ўласьцівасьці стану (state property) кампанэнтаў ды абнаўляецца толькі з дапамогай setState().

Мы можам аб’яднаць абодва, робячы стан (state) React’а “адзінай крыніцай праўды”. Да таго ж React-кампанэнт, які рэндэрыць форму, таксама кантралюе тое, што адбываецца ў гэнай форме пры наступным ўводзе карыстальніка (user input). Элемэнт формы ўводу (input form element), значэньне якога кантралюецца React’ам, такім чынам, называецца “кантралюемы кампанэнт”.

Напрыклад, калі мы хочам зрабіць, каб папярэдні прыклад рэгістраваў імя пры яго адпраўцы, то можам напісаць гэтую форму як кантралюемы кампанэнт:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

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

  handleSubmit(event) {
    alert('A name was submitted/Імя было адпраўлена: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name/Імя:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

Паспрабаваць на CodePen.

Паколькі атрыбут value ўсталёўваецца на наш элемэнт формы, то адлюстраванае значэньне заўсёды будзе this.state.value, робячы стан React’а крыніцаю праўды. Паколькі handleChange спрацоўвае на кожны націск клявішы, каб абнавіць стан React’а, то адлюстраванае значэньне будзе абнаўляцца па меры таго, як карыстальнік будзе набіраць на клявіятуры.

З кантралюемым кампанэнтам, кожнае зьмяненьне (mutation) стану будзе мець асацыяваную функцыю апрацоўшчыка (handler). Гэта робіць яго простым для мадыфікаваньня або праверкі ўводу карыстальніка. Напрыклад, калі мы хочам забясьпечыць напісаньне імёнаў вялікімі літарамі, то мы можам напісаць handleChange, як напрыклад:

handleChange(event) {
  this.setState({value: event.target.value.toUpperCase()});
}

Тэг textarea

У HTML’е элемэнт <textarea> вызначае ягоны тэкст па ягоных сыноўніх элемэнтах (children):

<textarea>
  Hello there, this is some text in a text area
  Прывітаньне, гэта тэкст у тэкставай вобласьці
</textarea>

У React’е <textarea> выкарыстоўвае замест гэтага атрыбут value. Такім чынам, форма, якая выкарыстоўвае <textarea>, можа быць напісаная вельмі падобна форме, што выкарыстоўвае аднарадковы ўвод (single-line input):

class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 'Please write an essay about your favorite DOM element. / Калі ласка, напішыце эсэ пра свой любімы элемэнт DOM.'
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

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

  handleSubmit(event) {
    alert('An essay was submitted/Эсэ было адпраўлена: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Essay/Эсэ:
          <textarea value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

Зьвярніце ўвагу, што this.state.value ініцыялізуецца ў канструктары, так што тэкставая вобласьць (text area) пачынаецца з тэксту ў ім.

Тэг select

У HTML’е <select> стварае спадальны сьпіс (drop-down list). Напрыклад, гэты HTML стварае спадальны сьпіс водараў:

<select>
  <option value="grapefruit">Grapefruit/грэйпфрут</option>
  <option value="lime">Lime/лайм</option>
  <option selected value="coconut">Coconut/какосавы</option>
  <option value="mango">Mango/манга</option>
</select>

Заўважце, што першапачаткова абраны варыянт (option) Coconut, з прычыны атрыбута selected. React замест таго, каб выкарыстоўваць гэты атрыбут selected, выкарыстоўвае атрыбут value каранёвага тэга select. Гэта больш зручна ў кантралюемым кампанэнце, таму што вам трэба абнаўляць яго толькі ў адным месцы. Напрыклад:

class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

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

  handleSubmit(event) {
    alert('Your favorite flavor is/Ваш улюбёны водар: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Pick your favorite flavor/Выберыце ваш улюбёны водар:
          <select value={this.state.value} onChange={this.handleChange}>
            <option value="grapefruit">Grapefruit/грэйпфрут</option>
            <option value="lime">Lime/лайм</option>
            <option value="coconut">Coconut/какосавы</option>
            <option value="mango">Mango/манга</option>
          </select>
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

Паспрабуйце на CodePen.

У цэлым, гэта робіць так, што <input type="text">, <textarea> ды <select> усе працуюць вельмі падобна — усе яны прымаюць атрыбут value, які вы можаце выкарыстоўваць для рэалізацыі кантралюемага кампанэнта.

Заўвага

Вы можаце перадаць масіў у атрыбут value, што дазволіць выбіраць некалькі варыянтаў у тэзе select:

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

Тэг file input

У HTML’е <input type="file"> дазваляе карыстальніку выбраць адзін альбо некалькі файлаў з прылады, дзе яны захоўваюцца, каб загрузіць іх на сэрвэр або маніпуляваць з дапамогай JavaScript праз File API.

<input type="file" />

Паколькі яго значэньне толькі для чытаньня, то гэта некантралюемы кампанэнт у React’е. Ён абмяркоўваецца разам зь іншымі некантралюемымі кампанэнтамі пазьней у дакумэнтацыі.

Апрацоўка мноства ўводаў (Multiple Inputs)

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

Напрыклад:

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

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

  render() {
    return (
      <form>
        <label>
          Is going/Зьбіраецца прыйсьці:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests/Колькасьць госьцяў:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

Паспрабуйце на CodePen.

Зьвярніце ўвагу, як мы выкарыстоўвалі сынтаксіс ES6 вылічанага імя уласьцівасьці, каб абнавіць ключ стану ў адпаведнасьці з дадзеным імем ўводу:

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

Што зьяўляецца эквівалентным гэтаму коду ES5:

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

Акрамя таго, паколькі setState() аўтаматычна аб’ядноўвае (merges) частковы стан зь бягучым станам, то нам неабходна толькі выклікаць яго са зьмененымі часткамі.

Значэньне Null кантралюемага ўводу (Input)

Указаньне ўласьцівасьці (prop) value ў кантралюемага кампанэнта засьцерагае карыстальніка ад зьмяненьня ўводу (input), пакуль вы гэтага не пажадаеце. Калі вы ўказалі value, але ўвод (input) па-ранейшаму даступны для рэдагаваньня, то магчыма вы выпадкова ўсталявалі для value значэньне undefined або null.

Наступны код гэта дэманструе. (Спачатку input заблякаваны, але становіцца даступным для рэдагаваньня пасьля кароткай затрымкі.)

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

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

Альтэрнатывы кантралюемым кампанэнтам

Часам можа быць стомным выкарыстоўваць кантралюемыя кампанэнты, таму што трэба напісаць апрацоўшчык падзей (event handler) для кожнага спосабу, якім могуць зьмяніцца вашы даныя, ды прапусьціць увесь стан ўводу (input state) праз React-кампанэнт. Гэта можа стаць асабліва раздражняльным, калі вы пераўтвараеце (converting) ўжо існуючую кодавую базу (codebase) ў React, альбо інтэгруеце прыкладную праграму React зь не-React бібліятэкай. У такіх сытуацыях вы можаце захацець спраўдзіць некантралюемыя кампанэнты, альтэрнатыўны мэтад для рэалізацыі формаў уводу (input forms).