- Published on
[Typescript] Function Overloads 그거 언제 쓰는거지?
- Authors

- Name
- Nostrss
- Github
- Github
Function Overloads란?
TypeScript에서 **Function Overloads(함수 오버로드)**는 하나의 함수 이름으로 서로 다른 매개변수 시그니처를 여러 개 정의하고, 이를 바탕으로 호출 시점에 적절한 타입을 결정하도록 돕는 기능이다.
자바나 C# 같은 언어에서는 컴파일러가 함수 이름과 파라미터 타입을 보고 자동으로 올바른 구현을 선택할수 있다고 한다. 하지만 자바스크에서는 런타임에 오직 하나의 구현부만 존재하기 때문에 이것이 불가능하다. 타입스크립트의 함수 오버로드는 이런 환경에서 같은 이름의 함수가 서로 다른 입력을 받을 때, 어떤 결과를 반환할지를 컴파일러 레벨에서 명확히 할 수 있도록 해준다.
- 오버로드 시그니처(Overload Signature): 호출부에서 어떤 파라미터 조합을 허용하고, 그에 대응하는 반환 타입은 무엇인지 선언하는 부분이다. 이 선언만으로 호출 시점에 타입 검사를 하고, 올바른 반환 타입을 추론할 수 있다.
- 참고로 오버로드 시그니처는 화살표 함수로 작성이 불가능하다고 한다.
function combine(a: string, b: string): string
function combine(a: number, b: number): number
- 구현 시그니처(Implementation Signature): 실제로 동작하는 함수 바디 부분이다. 일반적으로 함수를 선언하는 부분이라고 생각하면 이해가 쉬울 것 같다.
function combine(a: string | number, b: string | number) {
if (typeof a === 'string' && typeof b === 'string') {
// 문자열 두 개를 합쳐서 string 반환
return a + b
}
if (typeof a === 'number' && typeof b === 'number') {
// 숫자 두 개를 합쳐서 number 반환
return a + b
}
throw new Error('잘못된 매개변수 타입')
}
위의 함수를 실행하면 아래돠 같다.
const res1 = combine('hello', 'world')
const res2 = combine(10, 20)
const res3 = combine(10, 'world') // ❌ 컴파일 오류
// No overload matches this call.
// Overload 1 of 2, '(a: string, b: string): string', gave the following error.
// Argument of type 'number' is not assignable to parameter of type 'string'.
// Overload 2 of 2, '(a: number, b: number): number', gave the following error.
// Argument of type 'string' is not assignable to parameter of type 'number'.(2769)
// input.tsx(5, 10): The call would have succeeded against this implementation, but implementation signatures of overloads are not externally visible.
즉 위에 선언된 오버로드 시그니처 선언을 기억해뒀다가, 구현 시그니처가 호출되는 시점에 파라미터의 타입을 살펴보고 적절한 오버로드 시그니처를 적용해준다고 볼 수 있다.
Function Overloads를 언제 어떻게 사용하면 좋을까?
다양한 입력 타입을 하나의 함수로 처리할 때
실제 프로젝트에서는 하나의 함수가 문자열을 받아 처리하기도 하고, 숫자를 받아 처리하기도 하는 경우가 많다. 이럴 때 any나 string | number 같은 넓은 타입으로 선언해 버리면, 호출부에서는 반환 타입을 정확히 알기 어렵게 된다.
이럴때 오버로드 시그니처를 사용하면 문자열을 입력으로 주면 반드시 문자열을 반환한다거나 “숫자를 입력으로 주면 반드시 숫자를 반환한다”는 사실을 컴파일러에게 알려 줄 수 있다.
// 1) 오버로드 시그니처 선언
function parseValue(s: string): number
function parseValue(n: number): string
// 2) 구현 시그니처 작성
function parseValue(x: string | number): string | number {
if (typeof x === 'string') {
// 문자열을 숫자로 바꿔서 반환
const parsed = Number(x)
if (isNaN(parsed)) throw new Error('문자열을 숫자로 변환할 수 없습니다.')
return parsed
}
// 숫자를 문자열로 바꿔서 반환
return x.toString()
}
// 호출 예시
const num: number = parseValue('123') // 반환 타입이 number로 추론
const str: string = parseValue(456) // 반환 타입이 string으로 추론
parseValue(true) // ❌ 컴파일 오류: 시그니처 없음
매개변수 개수별로 다른 동작을 지원할 때
JavaScript 함수는 기본적으로 가변 인자(...args)를 지원하지만, TypeScript에서 (...args: any[]) => any로만 선언하면 타입 안정성이 전혀 없어진다. 오버로드 시그니처를 통해 “인자 두 개일 때는 이런 동작을 하고, 인자 세 개일 때는 이런 동작을 한다”라는 규칙을 컴파일러에게 알려 줄 수 있다.
// 1) 오버로드 시그니처 선언
function concatItems(a: string, b: string): string
function concatItems(a: string, b: string, c: string): string
function concatItems(a: number, b: number): number[]
function concatItems(a: number, b: number, c: number): number[]
// 2) 구현 시그니처 작성
function concatItems(a: string | number, b: string | number, c?: string | number) {
if (typeof a === 'string' && typeof b === 'string') {
// 문자열 2개 또는 3개를 합쳐서 문자열 반환
if (typeof c === 'string') {
return a + b + c
}
return a + b
}
if (typeof a === 'number' && typeof b === 'number') {
// 숫자 2개 또는 3개를 배열로 반환
if (typeof c === 'number') {
return [a, b, c]
}
return [a, b]
}
throw new Error('유효하지 않은 매개변수')
}
// 호출 예시
const s1 = concatItems('foo', 'bar') // s1: string
const s2 = concatItems('a', 'b', 'c') // s2: string
const n1 = concatItems(1, 2) // n1: number[]
const n2 = concatItems(3, 4, 5) // n2: number[]
concatItems('a', 1) // ❌ 컴파일 오류: 시그니처와 일치하지 않음
concatItems(1) // ❌ 컴파일 오류: 시그니처와 일치하지 않음
문자열이 들어오면 문자열을 합쳐서 string으로 반환하고, 숫자가 들어오면 숫자 배열(number[])로 반환한다.
다시 말하면 “두 개의 문자열” 혹은 “세 개의 문자열”을 받을 수 있고, “두 개의 숫자” 혹은 “세 개의 숫자”를 받을 수 있다는 걸 컴파일러가 미리 알고 있다는 것이다.
잘못된 파라미터 개수나 타입이 전달되면 “오버로드 시그니처와 일치하지 않는다”는 컴파일 오류가 발생한다.
제네릭 오버로드를 활용한 데이터 요청 함수
그리고 아래와 같이 제네릭을 이용하는 방법도 있을 수 있다.
// 1) 오버로드 시그니처 선언
function requestData(url: string): Promise<any>
function requestData<T>(url: string): Promise<T>
// 2) 실제 구현
async function requestData(url: string): Promise<any> {
const res = await fetch(url)
return res.json()
}
// 호출 예시
interface Post {
id: number
title: string
}
const post = await requestData<Post>('/api/posts/1') // post: Post
const rawData = await requestData('/api/raw') // rawData: any
아직 실무에서는 한 번도 사용해 본적이 없다.
하지만 언젠가 그때가 올 수 있지 않을까.
그때 잊지 않고 사용할 수 있겠지?
