Сейчас мы с вами начнем изучать работу с формами в фреймворке React. Начнем с простых инпутов и будем постепенно разбирать более сложные элементы форм.
Основы работы с формами
Пусть у нас в this.state.value хранится текст 'привет':
class App extends React.Component {
constructor() {
super();
this.state = {value: 'привет'};
}
}
Теперь в методе render сделаем инпут, пока просто выведем его на экран:
class App extends React.Component {
constructor() {
super();
this.state = {value: 'привет'};
}
//Выведем инпут:
render() {
return <div>
<input />
</div>;
}
}
Обратите внимание на то, что инпут надо закрывать с помощью обратного слеша, иначе при преобразовании JSX будет ошибка.
Давайте теперь сделаем так, чтобы в value инпута записалось значение из this.state.value:
class App extends React.Component {
constructor() {
super();
this.state = {value: 'привет'};
}
render() {
return <div>
<input value={this.state.value} />
</div>;
}
}
Запустите этот код - на экране вы увидите инпут с текстом 'привет'. Однако, вас ждет сюрприз: вы не сможете поменять текст нашего инпута. Попробуйте сами - повводите что-нибудь в этот инпут - текст просто не будет меняться.
Почему так? Потому что мы четко сказали, что в value инпута должно быть значение из this.state.value. Это значение не меняется - и значит value инпута тоже не будет меняться, даже если вы вручную что-то попытаетесь туда написать.
Понятно, что такое поведение не очень удобно и нам нужно что-то с этим сделать. Что именно: нужно организовать двухстороннее связывание - this.state.value и value инпута должны зависеть друг от друга: при изменении одного должен меняться и другой.
Первый шаг для этого следующий: нужно к нашему инпуту добавить событие onChange и привязать к нему какой-нибудь метод, назовем его, к примеру, handleChange.
Сделаем это:
class App extends React.Component {
constructor() {
super();
this.state = {value: 'привет'};
}
//Срабатывает при любом изменении инпута:
handleChange() {
//тут какой-то код
}
render() {
return <div>
<input value={this.state.value} onChange={this.handleChange.bind(this)} />
</div>;
}
}
Как работает событие onChange - оно срабатывает при попытке любого изменения инпута. Например, если мы пытаемся ввести в него какой-то текст, то onChange будет срабатывать при каждом вводе символа.
И, хотя текст инпута не будет меняться из-за привязанного this.state.value, событие onChange будет срабатывать и каждый раз вызывать метод handleChange.
Убедимся в этом - сделаем так, чтобы handleChange при каждом своем вызове выводил что-нибудь в браузер. Запустите следующий код, повводите что-нибудь в инпут и посмотрите в консоль - вы увидите, как при каждом вводе символа вызывается handleChange:
class App extends React.Component {
constructor() {
super();
this.state = {value: 'привет'};
}
//Выводим текст '!!!' каждый раз при изменении инпута:
handleChange() {
console.log('!!!');
}
render() {
return <div>
<input value={this.state.value} onChange={this.handleChange.bind(this)} />
</div>;
}
}
Давайте теперь сделаем так, чтобы содержимое инпута могло изменяться. Для этого в методе handleChange нужно в свойство this.state.value записывать содержимое атрибута value нашего инпута.
Однако, нас ждет некоторая неожиданность - мы не сможем получить value инпута так, как мы привыкли: такое - this.value - не будет работать, ведь this сейчас привязан с помощью bind к объекту нашего класса App и не указывает на инпут, в котором происходит событие.
Но как тогда обратиться к нашему инпуту? Ответ такой: с помощью объекта Event, вот так:
handleChange(event) {
console.log(event.target); //наш инпут;
console.log(event.target.value) //значение нашего инпута;
}
То есть: на наш инпут ссылается event.target, а значение инпута можно достать из event.target.value.
Вооружившись этим знаниями, привяжем теперь значение нашего инпута event.target.value к this.state.value с помощью метода this.setState:
handleChange(event) {
this.setState({value: event.target.value});
}
Давайте соберем все вместе и запустим наш код (сделайте это) - теперь данные в инпут можно будет вводить:
class App extends React.Component {
constructor() {
super();
this.state = {value: 'привет'};
}
//Изменяем this.state.value при изменении инпута:
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
return <div>
<input value={this.state.value} onChange={this.handleChange.bind(this)} />
</div>;
}
}
Итак, сейчас любые изменения инпута мгновенно приводят к изменению this.state.value. Поэтому, если мы где-нибудь в JSX коде выведем содержимое this.state.value - оно мгновенно будет изменятся при вводе текста в инпут.
Давайте сделаем над нашим инпутом абзац, в который будем выводить содержимое this.state.value, вот так:
render() {
return <div>
<p>текст инпута: {this.state.value}</p>
<input value={this.state.value} onChange={this.handleChange.bind(this)} />
</div>;
}
Добавим это изменение в наш класс:
class App extends React.Component {
constructor() {
super();
this.state = {value: 'привет'};
}
//Записываем value инпута в this.state.value:
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
return <div>
<p>текст инпута: {this.state.value}</p>
<input value={this.state.value} onChange={this.handleChange.bind(this)} />
</div>;
}
}
Запустите этот код, повводите текст в инпут и вы увидите, как он мгновенно меняется в абзаце над нашим инпутом.
Модификация данных на выводе
Данные на выводе в JSX коде можно модифицировать с помощью различных методов. Применим, к примеру, к тексту инпута метод toUpperCase. В этом случае текст будет выводится большими буквами:
render() {
return <div>
<p>текст инпута: {this.state.value.toUpperCase()}</p>
<input value={this.state.value} onChange={this.handleChange.bind(this)} />
</div>;
}
Добавим это изменение в наш класс:
class App extends React.Component {
constructor() {
super();
this.state = {value: ''}; //пусть инпут изначально пустой
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
return <div>
<p>текст инпута: {this.state.value.toUpperCase()}</p>
<input value={this.state.value} onChange={this.handleChange.bind(this)} />
</div>;
}
}
Запустите этот код, повводите текст в инпут и вы увидите, как он появляется в абзаце в верхнем регистре.
Отправка формы
Иногда может быть неудобным, что при введении каждого символа в инпут каждый раз что-то меняется на экране.
Во-первых, ввод данных еще не закончен и это может привести к неправильной работе скрипта.
Во-вторых, возможно, после ввода данных мы хотим провести какую-то ресурсоемкую операцию над ними. Очевидно, что не стоит выполнять ее после каждого ввода символа - лучше бы в этом случае дождаться окончания ввода.
Для избежания приведенных выше проблем сделаем следующее: создадим HTML форму с инпутом и кнопкой для отправки:
render() {
return <form>
<input value={this.state.value} onChange={this.handleChange.bind(this)} />
<input type="submit" />
</form>
}
Предполагается, что пользователь нашего сайта будет работать с нашей формой так: сначала он введет данные в инпут и затем нажмет на кнопку отправки. В этом случае в теге <form> сгенерируется событие onsubmit.
Давайте отловим это событие: напишем форме атрибут onSubmit и привяжем к нему метод handleSubmit:
render() {
return <form onSubmit={this.handleSubmit.bind(this)}>
<input value={this.state.value} onChange={this.handleChange.bind(this)} />
<input type="submit" />
</form>
}
Обратите внимание на то, к инпуту все равно привязан метод handleChange - это нужно для того, чтобы мы вообще что-то могли писать в этот инпут и данные автоматически заносились в this.state.value.
Напишем реализацию метода handleSubmit: при попытке отправки формы выведем алертом value нашего инпута из this.state.value, а затем вызовем event.preventDefault() для отмены отправки формы (если это не сделать - по нажатию на кнопку submit форма отправится и страница перезагрузится):
handleSubmit(event) {
alert(this.state.value); //выводим данные на экран
event.preventDefault(); //отменяем отправку формы
}
Итак, давайте соберем все вместе:
class App extends React.Component {
constructor() {
super();
this.state = {value: ''};
}
//Записываем value инпута в this.state.value:
handleChange(event) {
this.setState({value: event.target.value});
}
//Выводим this.state.value при отправке формы:
handleSubmit(event) {
alert(this.state.value); //выводим данные на экран
event.preventDefault(); //отменяем отправку формы
}
render() {
return <form onSubmit={this.handleSubmit.bind(this)}>
<input value={this.state.value} onChange={this.handleChange.bind(this)} />
<input type="submit" />
</form>
}
}
Запустите этот код, введите данные в инпут и нажмите на кнопку отправки - вы увидите алерт с текстом нашего инпута.
Добавление в список
Сейчас мы совместим вывод массивов в виде списка и инпут для добавления новых элементов в этот список.
Давайте вначале вспомним код, с помощью которого мы выводили массив в виде списка ul, а также добавляли новый элемент в список с помощью кнопочки. Текст у добавленного элемента был жестко задан и имел значение 'пункт':
class App extends React.Component {
constructor() {
super();
this.state = {items: [1, 2, 3, 4, 5]};
}
//Добавляем в конец списка новый пункт:
addItem() {
this.state.items.push('пункт');
this.setState({items: this.state.items});
}
render() {
//Формируем набор из тегов li:
const list = this.state.items.map((item, index) => {
return <li key={index}>{item}</li>;
});
//По нажатию на кнопку вызываем addItem:
return (
<div>
<ul>
{list}
</ul>
<button onClick={this.addItem.bind(this)}>добавить</button>
</div>
);
}
}
Добавим теперь в этот код форму, с помощью которой по нажатию на кнопку данные из инпута будут записываться в конец списка:
class App extends React.Component {
constructor() {
super();
this.state = {items: [1, 2, 3, 4, 5], value: ''};
}
//Добавляем в конец списка новый пункт:
addItem(event) {
//Добавляем новый элемент в массив:
this.state.items.push(this.state.value);
//Применяем изменение:
this.setState({items: this.state.items});
//Отменяем отправку формы:
event.preventDefault();
}
//Записываем value инпута в this.state.value:
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
//Формируем набор из тегов li:
const list = this.state.items.map((item, index) => {
return <li key={index}>{item}</li>;
});
//По нажатию на кнопку вызываем addItem:
return (
<div>
<ul>
{list}
</ul>
<form onSubmit={this.addItem.bind(this)}>
<input
value={this.state.value}
onChange={this.handleChange.bind(this)}
/>
<input type="submit" />
</form>
</div>
);
}
}
Запустите этот код, введите данные в инпут и нажмите на кнопку отправки - значение из инпута добавится в конец списка.