개발새발
bind 분석하기 본문
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는 이를 표현하기 위해 CallableFunction과 NewableFunction에 여러 오버로딩을 제공한다.
- 함수의 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 |