Вот почему нам нужно привязать обработчики событий в компонентах класса в React

1656625212 vot pochemu nam nuzhno privyazat obrabotchiki sobytij v komponentah klassa

автор Саураб Мисра

6zTp3-o8f4fKLBdGJJWln3nXtONaqALbEZdI
Фоновое фото от Kaley Dykstra на Unsplash, изображение исходного кода, созданное на carbon.now.sh

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

class Foo extends React.Component{
  constructor( props ){
    super( props );
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick(event){
    // your event handling logic
  }
  
  render(){
    return (
      <button type="button" 
      onClick={this.handleClick}>
      Click Me
      </button>
    );
  }
}

ReactDOM.render(
  <Foo />,
  document.getElementById("app")
);

В этой статье мы выясним, почему это необходимо сделать.

Я бы рекомендовал прочитать о .bind() здесь, если вы еще не знаете, что он делает.

Обвиняйте JavaScript, а не реагируйте

Ну, обвинение звучит несколько жестко. Это не то, что нам нужно делать из-за того, как работает React или через JSX. Это потому, как this связывание работает в JavaScript.

Давайте посмотрим, что произойдет, если мы не свяжем метод обработки событий с его экземпляром компонента:

class Foo extends React.Component{
  constructor( props ){
    super( props );
  }
    
  handleClick(event){
    console.log(this); // 'this' is undefined
  }
    
  render(){
    return (
      <button type="button" onClick={this.handleClick}>
        Click Me
      </button>
    );
  }
}

ReactDOM.render(
  <Foo />,
  document.getElementById("app")
);

Если вы запустите этот код, нажмите кнопку Click Me и проверьте свою консоль. Ты увидишь undefined напечатано на консоли как значение this изнутри метода обработчика событий. The handleClick() метод, кажется, есть потеряно его контекст (экземпляр компонента) или this значение.

Как привязка ‘this’ работает в JavaScript

Как я уже упоминал, это происходит через способ this связывание работает в JavaScript. Я не буду вдаваться в детали в этой публикации, но вот отличный ресурс, чтобы понять, как this связывание работает в JavaScript.

Но, что касается нашей дискуссии здесь, значение this внутри функции зависит от того, как вызывается эта функция.

Привязка по умолчанию

function display(){
 console.log(this); // 'this' will point to the global object
}

display(); 

Это простой вызов функции. Значение this внутри display() методом в этом случае является окно или глобальный объект в нестрогом режиме. В строгом режиме this значение есть undefined.

Неявная привязка

var obj = {
 name: 'Saurabh',
 display: function(){
   console.log(this.name); // 'this' points to obj
  }
};

obj.display(); // Saurabh 

Когда мы вызываем функцию таким образом, предшествует объект контекста, то this значение внутри display() установлено на obj.

Но когда мы присваиваем эту ссылку на функцию какой-то другой переменной и вызываем функцию с помощью этой новой ссылки на функцию, мы получаем другое значение this внутри display() .

var name = "uh oh! global";
var outerDisplay = obj.display;
outerDisplay(); // uh oh! global

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

Это особенно применимо при передаче таких функций, как обратные вызовы другой специальной функции, функции библиотеки сторонних разработчиков или встроенной функции JavaScript, например setTimeout .

Рассмотрим setTimeout фиктивное определение, как показано ниже, а затем вызовите его.

// A dummy implementation of setTimeout
function setTimeout(callback, delay){

   //wait for 'delay' milliseconds
   callback();
   
}

setTimeout( obj.display, 1000 );

Мы можем это понять, когда позвоним по телефону setTimeoutJavaScript внутренне назначает obj.display на его аргумент callback .

callback = obj.display;

Эта операция присвоения, как мы видели ранее, вызывает display() функция утратит свой контекст. Когда этот обратный вызов в конечном итоге вызывается внутри setTimeout, this значение внутри display() возвращается к связывание по умолчанию.

var name = "uh oh! global";
setTimeout( obj.display, 1000 );

// uh oh! global

Явная жесткая привязка

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

var name = "uh oh! global";
obj.display = obj.display.bind(obj); 
var outerDisplay = obj.display;
outerDisplay();

// Saurabh

Теперь, когда мы звоним outerDisplay()значение this указывает на obj внутри display() .

Даже если мы пройдем obj.display как обратный звонок, this значение внутри display() будет правильно указывать на obj .

Повторное создание сценария с помощью только JavaScript

В начале этой статьи мы видели это в нашем компоненте React под названием Foo . Если мы не связали обработчик событий с this его значение внутри обработчика события было установлено как undefined.

Как я уже упоминал и объяснял, это через способ this привязка работает в JavaScript и не связана с тем, как работает React. Давайте удалим специфический для React код и построим подобный чистый пример JavaScript, чтобы имитировать это поведение.

class Foo {
  constructor(name){
    this.name = name
  }
  
  display(){
    console.log(this.name);
  }
}

var foo = new Foo('Saurabh');
foo.display(); // Saurabh

// The assignment operation below simulates loss of context 
// similar to passing the handler as a callback in the actual 
// React Component
var display = foo.display; 
display(); // TypeError: this is undefined

Мы не моделируем реальные события и обработчики, а вместо этого используем синонимический код. Как мы наблюдали в примере компонента React, this значение было undefined поскольку контекст был утрачен после передачи обработчика как обратного вызова – синоним операции присвоения. Это то, что мы наблюдаем здесь, в этом фрагменте JavaScript, который не соответствует React.

«Подожди минутку! Не следует ли this значение указывает на глобальный объект, поскольку мы выполняем его в строгом режиме согласно правилам связывания по умолчанию?» вы можете спросить.

Нет. Вот поэтому:

Тела о декларации классов и выражения класса выполняются в строгом режиме, то есть конструктор, статический и прототипный методы. Функции Getter и Setter выполняются в жестком режиме.

Полную статью можно прочесть здесь.

Итак, чтобы предотвратить ошибку, нам нужно привязать this значение вот такое:

class Foo {
  constructor(name){
    this.name = name
    this.display = this.display.bind(this);
  }
  
  display(){
    console.log(this.name);
  }
}

var foo = new Foo('Saurabh');
foo.display(); // Saurabh

var display = foo.display;
display(); // Saurabh

Нам не нужно делать это в конструкторе, и мы можем это делать в другом месте. Учитывайте это:

class Foo {
  constructor(name){
    this.name = name;
  }
  display(){
    console.log(this.name);
  }
}

var foo = new Foo('Saurabh');
foo.display = foo.display.bind(foo);
foo.display(); // Saurabh

var display = foo.display;
display(); // Saurabh

Но конструктор является наиболее оптимальным и эффективным местом для кодирования наших операторов привязки обработчика событий, учитывая, что здесь происходит вся инициализация.

Почему нам не нужно связывать?this’ для стрелочных функций?

У нас есть еще два способа определения обработчиков событий внутри компонента React.

class Foo extends React.Component{
  handleClick = () => {
    console.log(this); 
  }
 
  render(){
    return (
      <button type="button" onClick={this.handleClick}>
        Click Me
      </button>
    );
  }
} 

ReactDOM.render(
  <Foo />,
  document.getElementById("app")
);
class Foo extends React.Component{
 handleClick(event){
    console.log(this);
  }
 
  render(){
    return (
      <button type="button" onClick={(e) => this.handleClick(e)}>
        Click Me
      </button>
    );
  }
}

ReactDOM.render(
  <Foo />,
  document.getElementById("app")
);

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

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

В случае синтаксиса полей открытого класса функция стрелки уложена внутри Foo класс – или функция конструктора – поэтому контекст – это экземпляр компонента, что мы и хотим.

В случае стрелочной функции в качестве примера обратного вызова стрелочная функция заключена внутри render() метод, который вызывает React в контексте экземпляра компонента. Вот почему функция стрелки также будет фиксировать этот же контекст и this значение внутри него будет правильно указывать на экземпляр компонента.

Подробнее о лексике this обязательно, посмотрите этот замечательный ресурс.

Если коротко

В компонентах класса в React, когда мы передаем ссылку на функцию обработчика событий как обратный вызов

<button type="button" onClick={this.handleClick}>Click Me</button>

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

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

Функции со стрелками освобождены от такого поведения, поскольку они используются лексический this связывание который автоматически привязывает их к области, где они определены.

Добавить комментарий

Ваш адрес email не будет опубликован.