icon
3장 : 함수와 타입

화살표 함수와 this


자바스크립트 개발에서 this 키워드는 종종 혼란을 야기하는 주범으로 꼽히곤 합니다. this의 값은 함수가 어떻게 호출되었는지에 따라 동적으로 결정되기 때문입니다. ES2015(ES6)에서 도입된 화살표 함수(Arrow Functions) 는 이러한 this 바인딩 방식에 큰 변화를 가져왔고, 타입스크립트 환경에서도 이 특성은 매우 중요합니다.

이번 절에서는 화살표 함수의 기본적인 사용법을 다시 한번 살펴보고, 특히 this 바인딩 규칙이 어떻게 달라지는지, 그리고 이것이 타입스크립트에서 어떤 의미를 가지는지 자세히 알아보겠습니다.


화살표 함수 기본 문법 복습

화살표 함수는 기존의 function 키워드 함수보다 더 간결한 문법으로 함수를 정의할 수 있게 해줍니다.

// 1. 기본 화살표 함수 (매개변수 2개, 반환 값 존재)
const add = (a: number, b: number): number => {
  return a + b;
};
console.log(add(5, 3)); // 8

// 2. 단일 매개변수 (괄호 생략 가능)
const square = (num: number): number => num * num;
console.log(square(4)); // 16

// 3. 매개변수 없음 (빈 괄호 필요)
const sayHello = (): string => "안녕하세요!";
console.log(sayHello()); // 안녕하세요!

// 4. 객체 리터럴 반환 (괄호로 감싸야 함)
const createPoint = (x: number, y: number) => ({ x: x, y: y });
console.log(createPoint(1, 2)); // { x: 1, y: 2 }

화살표 함수는 특히 콜백 함수나 짧은 함수를 정의할 때 유용하며, 코드를 훨씬 간결하고 읽기 쉽게 만듭니다.


this 바인딩의 차이점

this 키워드는 함수가 실행될 때 함수의 실행 컨텍스트(Execution Context) 에 따라 결정됩니다. 이 this 바인딩 방식에서 화살표 함수와 일반 함수는 결정적인 차이를 보입니다.

  • 일반 함수 (function 키워드 함수): this는 함수가 어떻게 호출되었는지(호출 방식) 에 따라 동적으로 결정됩니다.

    • 객체의 메서드로 호출될 때: this는 해당 객체를 가리킵니다.
    • 단독으로 호출될 때 (엄격 모드가 아닐 경우): this는 전역 객체(브라우저에서는 window, Node.js에서는 global)를 가리킵니다. 엄격 모드('use strict')에서는 undefined를 가리킵니다.
    • new 키워드와 함께 생성자 함수로 호출될 때: this는 새로 생성된 인스턴스를 가리킵니다.
    • call(), apply(), bind() 메서드를 사용할 때: this는 명시적으로 바인딩된 객체를 가리킵니다.
    class TraditionalGreeter {
      greeting: string = "Hello";
    
      sayGreeting(name: string) {
        console.log(`${this.greeting}, ${name}!`);
      }
    
      // 메서드를 콜백으로 전달할 때 문제가 발생할 수 있습니다.
      sayGreetingLater() {
        setTimeout(function() {
          // 여기서 this는 setTimeout에 의해 window (또는 undefined in strict mode)를 가리킵니다.
          // 따라서 this.greeting은 undefined가 됩니다.
          console.log(`${this.greeting}, world!`);
        }, 100);
      }
    }
    
    const tg = new TraditionalGreeter();
    tg.sayGreeting("TypeScript"); // Hello, TypeScript!
    // tg.sayGreetingLater(); // Uncaught TypeError: Cannot read properties of undefined (reading 'greeting')

    sayGreetingLater 메서드 내부의 setTimeout 콜백 함수에서 this.greetingundefined가 되는 것을 볼 수 있습니다. 이는 콜백 함수가 독립적으로 호출되면서 this의 컨텍스트가 변경되었기 때문입니다.

  • 화살표 함수: 화살표 함수는 자신만의 this 바인딩을 가지지 않습니다. 대신, 함수가 선언된 스코프(Lexical Scope)this 값을 상속(Lexical this) 받습니다. 즉, 화살표 함수 내부의 this는 항상 자신을 감싸는(바로 바깥) 일반 함수 또는 전역 스코프의 this와 동일합니다.

    class ArrowGreeter {
      greeting: string = "Hello";
    
      sayGreetingLater() {
        setTimeout(() => {
          // 화살표 함수이므로, sayGreetingLater 메서드의 this를 상속받습니다.
          // 따라서 여기서 this는 ArrowGreeter 인스턴스를 가리킵니다.
          console.log(`${this.greeting}, world!`);
        }, 100);
      }
    }
    
    const ag = new ArrowGreeter();
    ag.sayGreetingLater(); // 약 100ms 후: Hello, world!

    ArrowGreeter 클래스의 sayGreetingLater 메서드 내부에서 사용된 화살표 함수는 sayGreetingLater 메서드의 this를 그대로 상속받습니다. 덕분에 this.greeting이 올바르게 Hello 값을 참조할 수 있습니다. 이러한 this의 '자동 바인딩' 특성 때문에 화살표 함수는 이벤트 핸들러나 콜백 함수로 사용될 때 매우 편리합니다.


타입스크립트에서 this 타입 명시하기

타입스크립트는 함수의 this가 어떤 타입을 가리킬지 명시적으로 지정할 수 있는 기능을 제공합니다. 이는 특히 라이브러리를 만들거나 복잡한 콜백 패턴에서 this의 타입을 명확히 하고 싶을 때 유용합니다. this 매개변수는 함수 매개변수 목록의 가장 첫 번째에 위치하며, 실제 자바스크립트 코드로는 컴파일되지 않습니다.

interface MyButton {
  label: string;
  onClick: (this: MyButton, event: Event) => void; // this가 MyButton 타입임을 명시
}

class ButtonHandler {
  buttonLabel: string = "클릭 미";

  // 이벤트 핸들러 함수
  // 첫 번째 매개변수로 this: ButtonHandler를 명시하여
  // 이 함수의 this가 ButtonHandler 인스턴스임을 알려줍니다.
  handleClick(this: ButtonHandler, event: Event) {
    console.log(`버튼 '${this.buttonLabel}'이 클릭되었습니다.`);
    console.log(`이벤트 타입: ${event.type}`);
  }
}

const handler = new ButtonHandler();

// 실제로 HTML 버튼에 연결되는 상황을 가정
// document.getElementById('myButton').addEventListener('click', handler.handleClick);

// 가상 호출:
// 만약 handler.handleClick이 버튼의 컨텍스트에서 호출되었다면 (DOM 이벤트)
// 이 예시에서는 this가 MyButton에 바인딩되지 않으므로 오류가 발생할 수 있습니다.

// 올바르게 this를 바인딩하여 호출하는 방법
const boundHandleClick = handler.handleClick.bind(handler);
// boundHandleClick(new Event('click')); // 이 경우 this는 handler 인스턴스

// ---
// 다른 예시: 객체 리터럴 내에서 this 타입 명시
interface Calculator {
  value: number;
  add(this: Calculator, num: number): void;
}

const calc: Calculator = {
  value: 0,
  add(num) { // 여기서 this는 Calculator 타입으로 추론됩니다.
    this.value += num;
  },
};

calc.add(10);
console.log(calc.value); // 10

this: MyButton 또는 this: ButtonHandler와 같이 this 매개변수를 선언하면, 타입스크립트 컴파일러는 해당 함수가 호출될 때 this의 타입이 일치하는지 검사해줍니다. 이는 특히 this의 컨텍스트가 중요하거나 복잡한 상황에서 코드의 안정성을 크게 향상시킵니다.


화살표 함수와 this 타입 명시의 조합

화살표 함수는 기본적으로 렉시컬 this를 가지므로, 명시적인 this 타입 매개변수를 사용하는 경우는 일반 함수에 비해 드뭅니다. 하지만 인터페이스나 타입 별칭으로 함수의 시그니처를 정의할 때 화살표 함수 문법을 사용하면, 자연스럽게 this가 상위 스코프를 따르도록 유도할 수 있습니다.

interface Logger {
  logLevel: string;
  // 화살표 함수 문법으로 콜백 시그니처 정의
  logMessage: (message: string) => void;
}

class MyLogger implements Logger {
  logLevel: string = "INFO";

  constructor() {
    // 화살표 함수를 사용하여 this를 MyLogger 인스턴스에 바인딩
    this.logMessage = (message: string) => {
      console.log(`[${this.logLevel}] ${message}`);
    };
  }
}

const logger = new MyLogger();
logger.logMessage("데이터 로드 완료."); // [INFO] 데이터 로드 완료.

// 만약 MyLogger의 logMessage가 일반 함수였다면,
// constructor에서 this를 바인딩하는 과정이 필요했을 것입니다.
/*
class MyLoggerWithTraditionalFunction implements Logger {
  logLevel: string = "INFO";
  logMessage: (message: string) => void;

  constructor() {
    this.logMessage = function(message: string) {
      console.log(`[${this.logLevel}] ${message}`); // 여기서 this는 MyLogger 인스턴스가 아닐 수 있음
    }.bind(this); // 명시적으로 bind를 해줘야 합니다.
  }
}
*/

화살표 함수 덕분에 this 바인딩에 대한 고민을 줄이고 더 간결하고 직관적인 코드를 작성할 수 있게 됩니다.


화살표 함수와 this의 관계는 자바스크립트 및 타입스크립트에서 매우 중요한 개념입니다. 화살표 함수가 렉시컬 this를 가진다는 점을 명확히 이해하면 콜백 함수나 비동기 코드에서 this 컨텍스트를 유지하는 데 큰 도움이 됩니다. 필요한 경우에는 타입스크립트의 this 타입 명시 기능을 활용하여 코드의 안정성을 더욱 높일 수 있습니다.

이로써 3장 함수와 타입에 대한 모든 내용을 마쳤습니다.