Всеки, който започва с 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 е недефинирана“ ще възникне при използване на този код, но скоро ще говорим за най-добрия начин да я коригираме!

Това е компонент, който изобразява бутон с 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 е недефиниран“. Всички те имат плюсовете и минусите, които включват най-бързи поправки, най-добра производителност и най-чисти решения. Нека демонстрираме различните методи за разрешаване на тази грешка и да поговорим за плюсовете и минусите, свързани с всеки метод.

Свързване във функцията за изобразяване: „Бърза корекция“

Най-доброто решение за бързо коригиране е .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 решение, то се представя малко по-добре от третото решение „Clean Fix“, споменато по-горе. Основните разработчици на React винаги се фокусират първо върху оптимизирането на методите за най-добра практика, които са документирали.

Друго страхотно нещо за това решение е, че поддържа вашия метод за рендиране чист точно както прави решението „Clean Fix“.

Какво ще кажете за транспилерите, които автоматично обвързват вашите манипулатори на събития вместо вас?

Получавате транспилери, които могат автоматично да обвържат вашите манипулатори на събития вместо вас, което прави всички тези методи, споменати по-горе, напълно излишни. Те обаче не се препоръчват поради следните две основни причини.

Първо, тези транспилатори не се поддържат от основния екип за разработка на React. Следователно, ако основният екип за разработка направи някакви оптимизации на React. Вашият транспилиран код може да не следва най-добрите практики, определени от основния екип на React. По-добре е просто да се придържате към документираните стандарти, отколкото да използвате преки пътища като този.

Второ, не всеки отделен манипулатор на събития във вашите класове трябва да бъде обвързан с this! Само манипулатори на събития, които използват this, всъщност трябва да бъдат обвързани с this. Например, когато се използва this.state, или this.props или дори this.someOtherEventHandler().

Надяваме се, че това отговаря на въпроса: Кога не трябва да използвате .bind(this)? Когато this не се използва във вашия манипулатор на събития, вашият манипулатор на събития не е необходимо да бъде обвързан с this. Това е добро извинение да не използвате автоматично обвързващи транспилатори. Въпреки това, обикновено, когато имате манипулатор на събития, който не използва this в своята декларация. Това означава, че вероятно можете да го експортирате в отделен помощен файл или просто да го декларирате извън декларацията на вашия клас. Това ще я направи функция за многократна употреба и ще направи вашия клас по-малко свързан.

Редактиране: Наскоро написах публикация в блога на тема „Подизобразяване тук“. Това е добър пример за това, че не е нужно да.bind(this) на всичко във вашите React класове.

В заключение

Най-добрият начин да .bind(this) в React е да го направите в метода на конструктора на компонента. Това се препоръчва от екипа за разработка на ядрото на React и е методът, който осигурява най-добра производителност.

Въпреки че всъщност няма значение за по-малки приложения. Не може да навреди да изберете по-добра производителност, когато не прави компромис с четливостта и поддръжката на кода. Затова в този случай изберете препоръчаното решение!

Тази публикация е вдъхновена от концепции, описани в „Code Complete: A Practical Handbook of Software Construction, Second Edition“. Въпреки че тази книга е написана години преди разработката на ReactJS, съдържанието е вечнозелено и изключително ценно!

Кажете ми в коментарите по-долу кой е любимият ви начин за bind(this)!

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

Повече откъде идва това

Тази история е публикувана в Noteworthy, където хиляди идват всеки ден, за да научат за хората и идеите, оформящи продуктите, които обичаме.

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