개발새발
같은 이름의 함수를 여러 번 선언 할 수 있다 본문
TypeScript 함수 오버로딩: 언제, 왜, 어떻게 쓸까?
JavaScript에서는 함수의 인자 개수나 타입에 제약이 없다. 하지만 TypeScript에서는 매개변수의 타입과 개수를 명확히 지정해야 하므로, 복잡한 함수 시그니처를 다룰 때 오버로딩(Overloading)이 매우 유용해진다.
오버로딩이 필요한 이유
아래처럼 숫자 덧셈 또는 문자열 결합을 처리하는 함수를 생각해자.
function add(x: string | number, y: string | number): string | number {
return x + y;
}
이 코드에는 두 가지 문제점이 있다:
- + 연산자는 서로 다른 타입 (number + string)에 사용할 수 없기 때문에 컴파일 에러 발생
- add(1, '2') 같은 호출도 허용됨 → 의도와 다르게 동작할 수 있음
해결책: 함수 오버로딩
function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: any, y: any) {
return x + y;
}
- 오버로드 시그니처: 위 두 개 (x: number..., x: string...)
- 구현부: 실제 함수 본문은 any를 사용 (단, any로 선언해도 오버로드된 타입만 허용됨)
허용되는 호출
add(1, 2); // OK → number
add('1', '2'); // OK → string
허용되지 않는 호출
add(1, '2'); // ❌ 에러
add('1', 2); // ❌ 에러
에러 메시지에는 Overload 1 of 2, Overload 2 of 2 등이 포함되어, 어떤 시그니처도 일치하지 않음을 알려준다.
오버로딩 순서의 중요성
function example(param: string): string;
function example(param: string | null): number;
function example(param: string | null): string | number {
return param ? 'string' : 123;
}
const result = example('hello'); // 결과 타입: string
오버로딩 시 위에 선언된 시그니처가 우선 매칭 된다. 위 예제에서 'hello'는 string | null에도 해당되지만, string 시그니처가 먼저 매칭된다.
오버로딩 순서를 바꾸면?
function example(param: string | null): number;
function example(param: string): string;
...
const result = example('hello'); // 결과 타입: number ❗️
실제 반환값은 string인데 타입은 number가 되어 런타임 에러가 날 수 있으니 좁은 타입을 먼저 선언해야 안전하다.
인터페이스/타입 별칭으로도 오버로딩 가능
interface Add {
(x: number, y: number): number;
(x: string, y: string): string;
}
const add: Add = (x: any, y: any) => x + y;
type Add1 = (x: number, y: number) => number;
type Add2 = (x: string, y: string) => string;
type Add = Add1 & Add2;
const add: Add = (x: any, y: any) => x + y;
둘 다 동일하게 동작하며, add(1, 2)나 add('1', '2')는 OK, add(1, '2')는 안 된다.
오버로딩이 불필요한 경우
아래와 같이 단순히 유니언이나 옵셔널로 표현 가능한 상황에서는 굳이 오버로딩을 사용할 필요가 없다.
function a(param: string | number) {}
function errorA(param: string | number) {
a(param); // ✅ 문제 없음
}
function b(p1: string, p2?: number) {}
function errorB(p1: string, p2: number | undefined) {
b(p1, p2); // ✅ 문제 없음
}
반면 아래처럼 오버로딩하면 타입 오류가 발생한다.
function a(param: string): void;
function a(param: number): void;
function a(param: string | number) {}
function errorA(param: string | number) {
a(param); // ❌ string | number는 일치하는 오버로드 없음
}
유니언 타입을 오버로딩 시그니처에 직접 전달하면 일치하지 않음.
언제 오버로딩을 써야 할까?
상황 | 오버로딩 사용 여부 |
인자 타입과 반환값이 명확히 2개 이상으로 구분될 때 | ✅ 사용 추천 |
단순히 유니언 또는 옵셔널로 표현 가능할 때 | ❌ 사용 지양 |
다양한 인자 조합을 받지만 내부 구현이 동일할 때 | ❌ 조건 분기로 처리 추천 |
구현부와 오버로드가 정확히 일치할 수 있을 때 | ✅ 사용 가능 |
함수 오버로딩은 타입 안정성을 높이고, 개발자의 의도를 명확히 표현할 수 있다. 다만, 무조건적인 사용은 코드 복잡도만 높일 수 있으니, 아래 원칙에 따라 사용하자.
- 가능한 한 유니언/옵셔널로 표현하자
- 좁은 타입 → 넓은 타입 순으로 오버로딩 선언하자
- 구현부와 오버로드 시그니처는 항상 일치하도록 하자
'Typescript' 카테고리의 다른 글
공변성과 반공변성을 알아야 함수끼리 대입 할 수 있다 (0) | 2025.03.29 |
---|---|
콜백함수의 매개변수는 생략 가능하다 (0) | 2025.03.29 |
함수와 메서드를 타이핑하자 (0) | 2025.03.29 |
조건문과 비슷한 컨디셔널 타입이 있다 (0) | 2025.03.29 |
객체 간에 대입할 수 있는지 확인하는 법을 배우자 (0) | 2025.03.22 |