2.7 Обработка событий


Обработка событий в React-элементах очень похожа на обработку событий в DOM-элементах. Но есть несколько синтаксических отличий:


  • События React именованы с использованием верблюжьей нотации вместо нижнего регистра.
  • С помощью JSX в качестве обработчика события вы передаете функцию, а не строку

К примеру, данный HTML:


Код
        
  <button onclick="deleteAllUsers()">Удалить всех пользователей</button>
    

в React немного отличается:


Код
        
  <button onClick={deleteAllUsers}>Удалить всех пользователей</button>
    

Другое отличие состоит в том, что в React вы не можете возвратить false, чтобы предотвратить поведение по умолчанию. Вы должны явно вызывать preventDefault().

К примеру, для нативного HTML, чтобы предотвратить поведение ссылки по умолчанию – открытие новой страницы, вы можете написать:


Код
        
  <a href="#" onclick="console.log('Пользователь был удален.'); return false">
    Удалить пользователя
  </a>
    

В React это будет выглядеть так:


Код
        
  function DeleteUserLink() {
    function onClick(e) {
      e.preventDefault();
      console.log('Пользователь был удален.');
    }

    return (
      <a href="#" onClick={onClick}>Удалить пользователя</a>
    );
  }
    

Здесь e – это синтетическое событие. React определяет такие синтетические события в соответствии со спецификацией W3C. Поэтому о кросс-браузерной совместимости переживать не стоит. Изучите справочное руководство SyntheticEvent, чтобы узнать больше.

Как правило, в React не нужно вызывать addEventListener, чтобы добавить слушателей в DOM-элемент, после того как он был создан. Вместо этого, просто передайте слушатель элементу в методе отрисовки.

Когда вы определяете компонент, используя ES6-класс, общий паттерн таков: обработчик события должен быть методом класса. К примеру, наш компонент Conditioner отрисовывает кнопки button, которые позволяют пользователю регулировать текущую температуру:


Код
        
  class Conditioner extends React.Component {
    constructor(props) {
      super(props);
      this.state = {temperature: 0};

      // Привязка необходима, чтобы сделать this доступным в коллбэке
      this.onIncrease = this.onIncrease.bind(this);
      this.onDecrease = this.onDecrease.bind(this);
    }

    onIncrease(){
      this.setState(prevState => ({
        temperature: prevState.temperature + 1
      }))
    }

    onDecrease(){
      this.setState(prevState => ({
        temperature: prevState.temperature - 1
      }))
    }

    render() {
      return (
        <p>
          <h2>Текущая температура: {this.state.temperature}</h2>
          <button onClick={this.onDecrease}>-</button>
          <button onClick={this.onIncrease}>+</button>
        </p>
      );
    }
  }
    

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


Вы должны быть внимательны со значением this в JSX-коллбэках. В JavaScript, методы класса не привязаны по умолчанию. Если вы забудете привязать функцию this.onIncrease и передать её в onClick, то, когда эта функция будет вызвана, this будет undefined.

Это неспецифическое поведение для React. Это тот случай, когда функции работают как в JavaScript. Как правило, если вы ссылаетесь на метод без () после него, например, onClick={this.onIncrease}, вам необходимо привязать этот метод.

Если синтаксис привязки вас раздражает, есть два способа, как это обойти. Первый способ: использовать экспериментальный синтаксис инициализатора свойств, помогающий правильно привязывать коллбэки:


Код
        
  class Logger extends React.Component {
    //Такой синтаксис гарантирует, что "this" привязан к onLog
    //Внимание! это экспериментальный синтаксис!
    onLog = () => {
      console.log('объект:', this);
    }

    render() {
      return (<button onClick={this.onLog}>Лог</button>);
    }
  }
    

Этот синтаксис разрешен по умолчанию в Create React App.

Если вы не используете синтаксис инициализатора свойств, есть второй способ: передавать стрелочную функцию-коллбэк как свойство элемента:


Код
        
  class Logger extends React.Component {
    onLog () {
      console.log('объект:', this);
    }

    render() {
      //Такой синтаксис гарантирует, что "this" привязан к onLog
      return (<button onClick={(e) => this.onLog(e)}>Лог</button>);
    }
  }
    

Однако у этого синтаксиса есть одна проблема: при каждой отрисовке Logger, создается новый коллбэк. В большинстве случаев – всё будет в порядке. Тем не менее, если этот коллбэк передается как свойство в нижние компоненты, последние могут выполнять дополнительную перерисовку, что может снизить производительность. Чтобы этого избежать мы рекомендуем делать привязку в конструкторе или использовать синтаксис инициализатора свойств.



2.7.1 Передача аргументов в обработчики событий


Часто внутри цикла обработчику событий нужно передать дополнительный параметр. Например, если id является идентификатором строки, рабочими будут следующие варианты:


Код
        
 <button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
 <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
        
    

Две вышеуказанные строки эквивалентны. Первая использует стрелочную функцию, а вторая Function.prototype.bind.

В обоих случаях аргумент e, представляющий событие React, будет передан как второй аргумент после id. В стрелочной функции мы должны передавать его явно, а с bind любые дальнейшие аргументы передаются в функцию автоматически.

В случае использования инициализатора свойств, можно использовать и такой вариант:


Код
        
 <button onClick={this.deleteRow}>Delete Row</button>
        
    

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