Menu

TypeScript 함수 오버로딩: 오버로드 시그니처

TypeScript 함수 오버로드는 함수 하나에 각자 반환 타입을 가진 여러 호출 시그니처를 줍니다. 오버로드 시그니처와 구현의 패턴, 컴파일러가 검사하는 규칙, 유니언 매개변수가 더 나은 경우, 클래스의 오버로드를 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

TypeScript의 함수 오버로딩은 함수 하나에 호출 시그니처 여러 개를 쓰고 그 뒤에 구현 하나를 두는 것입니다. 시그니처마다 서로 다른 매개변수 타입과 서로 다른 반환 타입을 짝지을 수 있고, 호출하는 쪽은 정확한 것을 받습니다.

오버로드가 없다면 parse는 모든 호출에 대해 number | number[]를 반환하고, 결과를 직접 좁히기 전까지 one + 1은 오류일 것입니다.

오버로드 시그니처와 구현

오버로드된 함수에는 두 부분이 있습니다.

  1. 오버로드 시그니처: 지원하는 호출 형태마다 하나씩 있는 본문 없는 선언입니다. 호출하는 쪽이 쓸 수 있는 시그니처는 이것뿐입니다.
  2. 구현 시그니처: 본문이 있는 마지막 선언입니다. 매개변수는 오버로드가 받는 모든 것을 받아야 하고, 반환 타입은 모든 오버로드의 반환 타입을 포괄해야 합니다. 밖에서는 보이지 않습니다.

타입은 컴파일 시점에만 존재하므로 런타임에는 JavaScript 함수가 하나 있습니다. 구현은 무엇을 할지 정하기 위해 인수를 살펴봐야 합니다(typeof, Array.isArray, arguments.length...). 컴파일러는 오버로드와 구현이 일치하는지 검사합니다.

function format(value: string): string;
function format(value: number): number {
  return value;
}
// error TS2394: This overload signature is not compatible with its implementation signature.

해결책은 구현을 넓히는 것입니다: function format(value: string | number): string | number.

구현 시그니처는 호출할 수 없다

사람들이 가장 놀라는 규칙입니다. 호출은 오버로드 시그니처 중 하나에 단독으로 맞아야 하며, TypeScript는 시그니처들을 합치지 않습니다.

컴파일러는 이렇게 출력합니다.

index.ts(12,19): error TS2769: No overload matches this call.
  The last overload gave the following error.
    Argument of type 'string | string[]' is not assignable to parameter of type 'string[]'.
      Type 'string' is not assignable to type 'string[]'.

구현은 string | string[]를 받지만 호출하는 쪽은 그것을 볼 수 없습니다. 유니언을 받고 유니언을 반환하는 세 번째 오버로드를 추가하면 호출이 컴파일되고 [ 1, 2 ]를 출력합니다.

function parse(input: string): number;
function parse(input: string[]): number[];
function parse(input: string | string[]): number | number[];
function parse(input: string | string[]): number | number[] {
  return Array.isArray(input) ? input.map(Number) : Number(input);
}

매개변수 개수가 다른 경우

오버로드는 인수 개수가 다른 호출도 기술합니다. 여기서 날짜는 타임스탬프로 만들거나 연, 월, 일로 만들 수 있지만, 숫자 두 개로는 만들 수 없습니다.

선택적 매개변수 두 개를 가진 시그니처 하나라면 makeDate(2024, 3)을 받아들이고 조용히 잘못된 날짜를 만들 것입니다. 오버로드는 이를 컴파일 오류(TS2575)로 바꿉니다.

순서가 중요하다

TypeScript는 위에서 아래로 오버로드를 시도하고 처음 맞는 것을 고릅니다. 가장 구체적인 시그니처를 먼저 두세요. 목록 앞쪽의 넓은 오버로드는 뒤에 오는 오버로드를 위한 호출을 가로챕니다.

function describe(value: unknown): string;   // matches everything
function describe(value: string): "text";    // never chosen
function describe(value: unknown): string {
  return typeof value === "string" ? "text" : "other";
}

const d = describe("hi"); // d: string, not "text"

앞의 두 시그니처를 맞바꾸면 describe("hi")의 타입은 "text"가 됩니다.

오버로드냐 유니언 매개변수냐

오버로드는 반환 타입이 인수 타입에 따라 달라질 때 늘어나는 줄 수만큼의 값을 합니다. 그렇지 않다면 유니언 매개변수를 쓰는 시그니처 하나가 더 짧고 읽기 쉬우며, 오버로드가 거부할 유니언 인수도 받습니다.

쓸 것언제
유니언 매개변수모든 입력에 반환 타입이 같을 때
선택적 매개변수호출 형태가 자유롭게 생략할 수 있는 뒤쪽 인수로만 다를 때
오버로드반환 타입이 인수에 따라 바뀌거나, 일부 인수 조합을 거부해야 할 때
제네릭identity<T>(x: T): T처럼 반환 타입이 인수 타입으로 만들어질 때

조건부 타입을 쓰는 제네릭은 일부 오버로드 묶음을 시그니처 하나로 표현할 수 있지만, 경우가 두세 개라면 대개 오버로드가 읽기 쉽습니다.

메서드와 생성자 오버로드

클래스 안의 메서드도 같은 패턴을 씁니다. 오버로드 시그니처를 쓰고, 본문이 있는 메서드를 둡니다. 생성자도 같은 방식으로 오버로드할 수 있습니다.

인터페이스와 객체 타입도 여러 호출 시그니처나 같은 이름의 여러 메서드 시그니처로 오버로드를 선언할 수 있습니다. 많은 내장 함수가 이렇게 선언되어 있습니다. 에디터에서 배열의 reduce에 마우스를 올리면 "+2 overloads"가 보입니다.

자주 묻는 질문

TypeScript는 함수 오버로딩을 지원하나요?

네, 타입 수준에서 지원합니다. 오버로드 시그니처(본문 없는 선언) 여러 개를 쓰고 그 뒤에 구현 하나를 둡니다. 호출하는 쪽은 오버로드 시그니처만 봅니다. 런타임에는 여전히 JavaScript 함수가 하나뿐이므로, 구현이 직접 인수를 확인하고 모든 경우를 처리합니다.

"No overload matches this call"은 무슨 뜻인가요?

오류 TS2769로, 인수가 어떤 오버로드 시그니처에도 맞지 않는다는 뜻입니다. 구현 시그니처는 계산에 들어가지 않으므로, 구현이 string | string[] 같은 유니언 인수를 받더라도 그런 인수로 호출하면 실패합니다. 유니언을 받는 오버로드를 추가하거나, 오버로드를 시그니처 하나로 바꾸세요.

언제 유니언 타입 대신 오버로드를 써야 하나요?

넘긴 인수 타입에 따라 반환 타입이 달라질 때 오버로드를 쓰세요. 예를 들어 string을 넣으면 number가 나오고 string[]를 넣으면 number[]가 나오는 경우입니다. 모든 입력에 대해 반환 타입이 같다면 유니언 매개변수를 쓰는 시그니처 하나가 더 단순하고 유니언 인수도 받습니다.

TypeScript에서 화살표 함수를 오버로드할 수 있나요?

오버로드 선언 문법으로는 안 됩니다. 그 문법은 function 선언과 메서드에서만 동작합니다. 변수에 여러 호출 시그니처를 가진 타입(type Parse = { (s: string): number; (s: string[]): number[] })을 줄 수는 있지만, 화살표 함수를 대입하려면 대개 타입 단언이 필요하므로 function 선언이 더 깔끔합니다.

오버로드 시그니처가 구현 시그니처와 호환되지 않는다는 오류는 왜 나나요?

오류 TS2394는 오버로드 하나가 구현이 받지 않거나 반환하지 않는 무언가를 받거나 반환한다는 뜻입니다. 구현의 매개변수는 모든 오버로드의 매개변수를 받아야 하고, 반환 타입은 모든 오버로드의 반환 타입과 호환되어야 합니다. 구현을 (보통 유니언으로) 넓히면 해결됩니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기