Любой, кто начинает работать с React, неизбежно столкнется с ошибкой отсутствия привязки this к своим обработчикам событий. Я уверен, что 90% из вас, читающих этот пост, сталкивались с этой неприятной "'this' inside your Component event handler is undefined" ошибкой. Остальные 10% читающих просто не являются разработчиками React.

Чтобы проиллюстрировать возможные решения этой проблемы. Я создал этот простой компонент ниже, который еще не привязан this:

import React, { Component } from 'react';
export default class CounterButton extends Component {
  state = { counter: 0 } 
  incrementCounter() {
    this.setState(prevState => ({
      counter: prevState.counter + 1,
    }));
  }
  render() {
    return (
      <Button onClick={this.incrementCounter}>
        {this.state.counter}
      </Button>
    );
  }
}

При использовании этого кода будет возникать ошибка «this is undefined», но мы скоро обсудим лучший способ ее исправить!

Это компонент, который отображает кнопку с counter, по умолчанию равным 0. После нажатия кнопки запускается обработчик событий incrementCounter. Этот обработчик событий увеличивает значение counter в состоянии компонента. Это приведет к повторному рендерингу компонента с новым значением состояния. Новое значение состояния - это предыдущее incrementCounter значение состояния плюс 1.

Последняя вещь! Большинство людей находят странным использование this.setState(prevState => ({ counter: prevState.counter + 1 })) для обновления состояния компонента. Почему бы просто не сказать this.setState({ counter: this.state.counter + 1 })? Я направлю вас к этому фантастическому твиту Дэна Абрамова, который является одним из основных разработчиков ReactJS.

Как решить ошибку «this не определено».

В современных классах React существует около 4 стандартных способов решения ошибки «this is undefined». У всех есть свои плюсы и минусы, которые включают в себя самые быстрые исправления, лучшую производительность и самые чистые решения. Давайте продемонстрируем различные методы решения этой ошибки и поговорим о плюсах и минусах каждого метода.

Подключите функцию рендеринга: «Быстрое исправление»

Лучшее решение для быстрого исправления - это .bind() в функции рендеринга следующим образом:

...
render() {
  return (
    <Button onClick={this.incrementCounter.bind(this)}>
      {this.state.counter}
    </Button>
  );
}
...

Это, безусловно, быстрое решение для запуска вашей программы. Теперь вы можете продолжить и прекратить досадную ошибку undefined this. Он также довольно чистый, хотя и добавляет некоторую логику к вашей функции рендеринга, которая может загромождать вещи по мере того, как ваше приложение становится больше.

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

Функция стрелки в рендере: другое «быстрое исправление»

Другой .bind() в функции рендеринга - использовать стрелочную функцию в методе рендеринга следующим образом:

...
render() {
  return (
    <Button onClick={() => this.incrementCounter()}>
      {this.state.counter}
    </Button>
  );
}
...

Этот метод также является очень быстрым решением и имеет тот же едва заметный удар по производительности, что и предыдущий метод. Некоторым людям не нравится функция .bind(this), потому что они никогда не понимают, что она означает. Однако, по моему честному мнению, это также делает ваш метод рендеринга немного загроможденным.

Функция стрелки в свойстве класса: «Чистое исправление»

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

...
export default class CounterButton extends Component {
  state = { counter: 0 }

  incrementCounter = () => {
    this.setState(prevState => ({
      counter: prevState.counter + 1,
    }));
  }
  render() {
    return (
      <Button onClick={this.incrementCounter}>
...

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

Привязать конструктор: «Рекомендуемое исправление»

Следующий метод рекомендуется в документации ReactJS и был тщательно протестирован, чтобы быть лучшим решением.

...
export default class CounterButton extends Component {
  state = { counter: 0 }
  constructor(props) {
    super(props);
    this.incrementCounter = this.incrementCounter.bind(this);
  }
  incrementCounter() {
    this.setState(prevState => ({
      counter: prevState.counter + 1,
    }));
  }
  render() {
    return (
      <Button onClick={this.incrementCounter}>
...

Это лучший способ делать что-то. Это определенно не самое быстрое и не самое чистое решение. Но, учитывая способ работы React, логично, что это будет наиболее эффективный метод. Обработчик событий привязывается к this при создании компонента. Это означает, что это происходит только один раз, что делает его лучше, чем первые два решения «Быстрое исправление», упомянутые выше. Поскольку это также рекомендованное React решение, оно работает немного лучше, чем третье решение «Чистое исправление», упомянутое выше. Основные разработчики React всегда в первую очередь сосредотачиваются на оптимизации лучших методов, которые они задокументировали.

Еще одна замечательная особенность этого решения заключается в том, что оно сохраняет ваш метод рендеринга в чистоте, как и решение «Чистое исправление».

А как насчет транспилеров, которые автоматически связывают ваши обработчики событий?

У вас есть транспилеры, которые могут автоматически связывать ваши обработчики событий за вас, что делает все эти методы, упомянутые выше, полностью избыточными. Однако это не рекомендуется по двум основным причинам.

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

Во-вторых, не каждый обработчик событий в ваших классах должен быть привязан к this! Только обработчики событий, которые используют this, фактически должны быть привязаны к this. Например, когда используется this.state, или this.props, или даже this.someOtherEventHandler().

Надеюсь, это ответит на вопрос: когда не следует использовать .bind(this)? Если this не используется в вашем обработчике событий, ваш обработчик событий не должен быть привязан к this. Это хороший повод не использовать транспайлеры с автоматической привязкой. Однако обычно, когда у вас есть обработчик событий, который не использует this в своем объявлении. Это означает, что вы, вероятно, можете экспортировать его в отдельный вспомогательный файл или просто объявить его вне объявления вашего класса. Это сделало бы эту функцию более пригодной для повторного использования и сделало бы ваш класс менее связанным.

Изменить: я недавно написал в блоге сообщение о Sub Rendering здесь. Это хороший пример того, что вам не нужно .bind(this) обо всем в ваших классах React.

В заключение

Лучший способ .bind(this) в React - сделать это в методе конструктора компонента. Это рекомендуется командой разработчиков ядра React, и это метод, обеспечивающий наилучшую производительность.

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

Этот пост был вдохновлен концепциями, изложенными в Code Complete: Практическое руководство по построению программного обеспечения, второе издание. Несмотря на то, что эта книга была написана за много лет до того, как был разработан ReactJS, ее содержание всегда актуально и чрезвычайно ценно!

Дайте мне знать в комментариях ниже, какой ваш любимый способ bind(this)!

Первоначально опубликовано на www.barrymichaeldoyle.com 28 августа 2018 г.

Подробнее откуда это взялось

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

Следите за нашей публикацией, чтобы увидеть больше историй о продуктах и ​​дизайне, представленных командой Journal.