ProgrammazioneFrontend разработчик

Как устроен механизм типизации this в методах классов TypeScript при использовании обычных и стрелочных функций? Опишите подводные камни и best practice.

Supera i colloqui con l'assistente IA Hintsage

Ответ.

В TypeScript для методов внутри класса тип this по умолчанию — это текущий экземпляр класса. Однако, если определять методы через стрелочные функции, контекст this будет привязан к моменту объявления, а не вызова.

Обычный метод:

class Counter { value = 0; increment() { this.value++; } }

Стрелочная функция:

class Counter { value = 0; increment = () => { this.value++; } }

Тонкости:

  • Обычные методы теряют контекст this при передаче их как колбэков (например, в event listener'ы).
  • Стрелочные сохраняют контекст заявленного класса, но не видны в прототипе, а создаются на каждый экземпляр.
  • Можно явно указать тип this в сигнатуре метода для дополнительной проверки:
class Foo { bar(this: Foo) { // ... } }

Вопрос с подвохом.

Чем отличается increment() и increment = () => {} в классе? Как это сказывается на контексте this при использовании в качестве колбэка?

Неправильный ответ:

  • "В поле класса и в методе разницы нет, TypeScript сам всё понимает."

Правильный ответ:

  • У обычного метода контекст this определяется в момент вызова. Если передать метод как функцию: setTimeout(counter.increment, 0), то this станет undefined (в строгом режиме) или window (в нестрогом), а стрелочная функция сохранит своё окружение:
class Demo { value = 1; inc() { console.log(this.value); } incArrow = () => { console.log(this.value); } } const d = new Demo(); setTimeout(d.inc, 0); // undefined или ошибка setTimeout(d.incArrow, 0); // 1

Примеры реальных ошибок из-за незнания тонкостей темы.


История

В проекте с реактивными фреймворками методы класса передавались напрямую как колбэки без явного bind. В результате this становился undefined, и приложение падало с ошибкой при доступе к this-свойствам. Проблему решили переписыванием методов на стрелочные либо явным bind.


История

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


История

Большой UI-компонент замедлял приложение, потому что все методы были описаны как стрелочные поля класса (new Counter().increment = ...), что создавало новые копии функций для каждого экземпляра, а не одно определение на прототипе, как с обычным методом. В результате увеличилось потребление памяти, и понадобилась оптимизация.