개발새발

bind 분석하기 본문

Typescript

bind 분석하기

비숑주인 2025. 5. 10. 22:42

JavaScript의 bind 메서드는 함수의 this 컨텍스트를 명시적으로 고정하고, 일부 인수를 미리 채워넣는 역할을 한다. 그런데 TypeScript에서는 이 동작을 타입 수준에서도 정확히 표현하기 위해 매우 다양한 오버로딩을 제공한다.

이번 글에서는 TypeScript 5.0.4 기준으로 bind 메서드가 어떻게 정의되어 있는지, 왜 그렇게 복잡한 오버로딩이 필요한지를 하나씩 분석해본다.

 

간단한 예제

function a(this: Window | Document) {
  return this;
}
const b = a.bind(document);
const c = b(); // c: Document

 

TypeScript는 bind를 통해 this를 바인딩하면, 이후 호출에서 해당 this가 고정되었다고 판단한다. 이를 타입 수준에서 표현하기 위해 다양한 오버로딩이 필요하다.

bind 메서드의 선언 위치

interface CallableFunction extends Function {
  bind<T>(this: T, thisArg: ThisParameterType<T>): OmitThisParameter<T>;
  bind<T, A0, A extends any[], R>(
    this: (this: T, arg0: A0, ...args: A) => R,
    thisArg: T,
    arg0: A0
  ): (...args: A) => R;
  // ...
}

 

이처럼 CallableFunction은 bind에 대한 여러 오버로딩을 제공한다. 인수 개수별로 다양한 오버로드가 존재하며, 내부적으로 ThisParameterType과 OmitThisParameter 같은 유틸리티 타입도 함께 사용한다.

ThisParameterType<T>와 OmitThisParameter<T>

type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;

type OmitThisParameter<T> =
  unknown extends ThisParameterType<T> ? T :
  T extends (...args: infer A) => infer R ? (...args: A) => R : T;

 

  • ThisParameterType<T>는 함수에서 this 타입을 추출한다.
  • OmitThisParameter<T>는 기존 함수 타입에서 this를 제거한 형태를 만든다.

이렇게 해야 bind로 this를 고정한 함수가 이후 this 인수를 받지 않도록 정확히 표현할 수 있다.

인수 바인딩 테스트

function add(a = 0, b = 0, c = 0, d = 0, e = 0) {
  return a + b + c + d + e;
}

const add1 = add.bind(null, 1);
const add2 = add.bind(null, 1, 2);
const add3 = add.bind(null, 1, 2, 3);
const add4 = add.bind(null, 1, 2, 3, 4);
const add5 = add.bind(null, 1, 2, 3, 4, 5);

 

반환 함수 타입 추론 결과

바인딩 단계 바인딩 된 인수 남은 인수 반환 타입
add1 1 (b?, c?, d?, e?) number
add2 1, 2 (c?, d?, e?) number
add3 1, 2, 3 (d?, e?) number
add4 1, 2, 3, 4 (e?) number
add5 1, 2, 3, 4, 5 없음 (완전한 적용) () => number
add5(99) 추가 인수 허용됨 (❌) (1 | 2 | 3 | 4 | 5)[] number
 

add5는 이상하게도 추가 인수를 받아도 타입 에러가 발생하지 않는다. 이유는 bind<T, AX, R>(..., ...args: AX[]): (...args: AX[]) => R 형태의 포괄적인 오버로드가 사용되었기 때문이다. TypeScript는 오버로딩 수가 너무 많아지는 것을 방지하기 위해 5개까지만 구체적으로 정의하고, 그 이상은 포괄 타입으로 타협한다.

클래스 생성자에 대한 bind

class A {
  constructor(public a: number) {}
}

const A0 = A.bind(null);      // typeof A
const a0 = new A0(1);

const A1 = A.bind(null, 1);   // new () => A
const a1 = new A1();

 

NewableFunction의 bind 정의

interface NewableFunction extends Function {
  bind<T>(this: T, thisArg: any): T;
  bind<A0, A extends any[], R>(
    this: new (arg0: A0, ...args: A) => R,
    thisArg: any,
    arg0: A0
  ): new (...args: A) => R;
  // ...
}

 

생성자 함수에 대한 bind는 this를 무시한다. 자바스크립트에서 클래스 생성자에 this 바인딩은 무시되기 때문에 타입스크립트도 thisArg: any로 처리하고 ThisParameterType 등을 사용하지 않는다.

마무리 정리

  • bind는 함수의 this를 고정하고, 인수를 일부 미리 설정하는 기능이다.
  • TypeScript는 이를 표현하기 위해 CallableFunctionNewableFunction에 여러 오버로딩을 제공한다.
  • 함수의 this 타입 제거를 위해 ThisParameterType과 OmitThisParameter가 사용된다.
  • bind의 오버로드는 실무에서 자주 쓰는 인수 개수(최대 4개)까지만 명확히 타이핑하고, 그 이상은 범용 타입으로 처리한다.
  • 클래스는 this 바인딩을 무시하므로 thisArg는 타입 검사에서 제외된다.

'Typescript' 카테고리의 다른 글

jQuery 타입 분석하기  (0) 2025.05.10
tsc와 tsconfigjson  (0) 2025.05.10
Promise, Awaited 타입 분석하기  (0) 2025.05.10
flat 분석하기  (0) 2025.05.10
reduce 만들기  (0) 2025.04.30