C나 Java같은 정적 타입 언어의 경우, 매개변수의 갯수와 타입에 따라서 정적으로 여러 개의 함수를 구현할 수 있습니다. 하지만 JavaScript는 하나의 함수 내부에서 타입 검사를 해서 동적으로 오버로딩을 구현할 수 있습니다.
TypeScript의 경우, 오버로딩이 불가능하지는 않지만 그 방식은 정적 타입 언어인 C나 Java보다는 동적 타입언어인 JavaScript와 유사합니다. 다른 인자를 받는 여러 개의 함수를 구현하는 것이 아니라, 하나의 함수에서 인자의 타입이나 갯수에 따라 여러 분기를 태우는 것입니다.
일단 TypeScript는 함수에 정해진 타입과 정해진 수의 인자를 넘기지 않으면 에러를 발생시키므로 함수를 선언할 때 어느정도의 유연성을 확보해야 오버로딩을 구현할 수 있습니다. 인자 갯수의 유연성을 확보하는 것이 바로 Optional Parameter 입니다.
// 함수 선언(interface)functionadd(a:string, b:string):string;functionadd(a:number, b:number):number;// 함수 구현(implement)functionadd(a:any, b:any){return a + b;}// 함수 호출add('2','3');add(2,3);add('2',3);/**
이 호출과 일치하는 오버로드가 없습니다.
오버로드 1/2('(a: string, b: string): string')에서 다음 오류가 발생했습니다.
'number' 형식의 인수는 'string' 형식의 매개 변수에 할당될 수 없습니다.
오버로드 2/2('(a: number, b: number): number')에서 다음 오류가 발생했습니다.
'string' 형식의 인수는 'number' 형식의 매개 변수에 할당될 수 없습니다.ts(2769)
a.ts(3, 10): 이 구현에 대한 호출이 성공하겠지만, 오버로드의 구현 시그니처는 외부에 표시되지 않습니다.
*/// .JSfunctionadd(a, b){return a + b;}// .D.TSdeclarefunctionadd(a:string, b:string):string;declarefunctionadd(a:number, b:number):number;
CASE 2 : 함수 표현식
// 선언한 것의 이름을 대변하는 이름typeSAdd=(a:string, b:string)=>string;typeNAdd=(a:number, b:number)=>number;// function SAdd(a: any, b: any) { return a + b } // 이와 같이 구현하면 타입 추론이 올바르게 되지 않습니다.// 구현 X (익명함수일 뿐이지 선언한 것을 구현했다고 볼수 없습니다)const add: SAdd &NAdd=function(a:any, b:any){return a + b;};add('2','3');add(2,3);add('2',3);/**
이 호출과 일치하는 오버로드가 없습니다.
오버로드 1/2('(a: string, b: string): string')에서 다음 오류가 발생했습니다.
'number' 형식의 인수는 'string' 형식의 매개 변수에 할당될 수 없습니다.
오버로드 2/2('(a: number, b: number): number')에서 다음 오류가 발생했습니다.
'string' 형식의 인수는 'number' 형식의 매개 변수에 할당될 수 없습니다.ts(2769)
*/// .JSconstadd=function(a, b){return a + b;};// .D.TSdeclaretypeSAdd=(a:string, b:string)=>string;declaretypeNAdd=(a:number, b:number)=>number;declareconst add: SAdd & NAdd;
CASE 3 : 인터섹션 안 쓰기
typeAdd={(a:string, b:string):string;(a:number, b:number):number;};const add:Add=function(a:any, b:any){return a + b;};add('2','3');add(2,3);add('2',3);/**
이 호출과 일치하는 오버로드가 없습니다.
오버로드 1/2('(a: string, b: string): string')에서 다음 오류가 발생했습니다.
'number' 형식의 인수는 'string' 형식의 매개 변수에 할당될 수 없습니다.
오버로드 2/2('(a: number, b: number): number')에서 다음 오류가 발생했습니다.
'string' 형식의 인수는 'number' 형식의 매개 변수에 할당될 수 없습니다.ts(2769)
*/// .JSconstadd=function(a, b){return a + b;};// .D.TSdeclaretypeAdd={(a:string, b:string):string;(a:number, b:number):number;};declareconst add: Add;
CASE 4 : 타입 바로 선언하기
const add:{(a:string, b:string):string;(a:number, b:number):number;}=function(a:any, b:any){return a + b;};add('2','3');add(2,3);add('2',3);/**
이 호출과 일치하는 오버로드가 없습니다.
오버로드 1/2('(a: string, b: string): string')에서 다음 오류가 발생했습니다.
'number' 형식의 인수는 'string' 형식의 매개 변수에 할당될 수 없습니다.
오버로드 2/2('(a: number, b: number): number')에서 다음 오류가 발생했습니다.
'string' 형식의 인수는 'number' 형식의 매개 변수에 할당될 수 없습니다.ts(2769)
*/// .JSconstadd=function(a, b){return a + b;};// .D.TSdeclareconst add:{(a:string, b:string):string;(a:number, b:number):number;};
함수 선언식 vs 함수 표현식
함수 선언식 - Function Declarations
일반적인 프로그래밍 언어에서의 함수 선언과 비슷한 형식입니다.
function함수명(){// 구현 로직}
함수 표현식 - Function Expressions
유연한 자바스크립트 언어의 특징을 활용한 선언 방식입니다.
var함수명=function(){// 구현 로직};
함수 선언식과 표현식의 차이점
호이스팅
함수 선언식은 호이스팅에 영향을 받지만, 함수 표현식은 호이스팅에 영향을 받지 않습니다.
함수 선언식은 코드를 구현한 위치와 관계없이 자바스크립트의 특징인 호이스팅에 따라 브라우저가 자바스크립트를 해석할 때 맨 위로 끌어 올려집니다.
함수 표현식에서 변수 선언도 호이스팅이 적용되어 위치가 상단으로 끌어올려집니다. 하지만 function 로직은 호출된 이후에 선언됩니다.
함수 표현식의 장점
클로저로 사용
콜백으로 사용(다른 함수의 인자로 넘길 수 있습니다)
이펙티브 타입스크립트 아이템 12 : 함수 표현식에 타입 적용하기
타입스크립트에서는 함수 표현식을 사용하는 것이 좋습니다. 함수의 매개변수 부터 반환값까지 전체를 함수 타입으로 선언하여 함수 표현식에 재사용할 수 있다는 장점이 있습니다.
매개변수나 반환 값에 타입을 명시하기보다는 함수 표현식 전체에 타입 구문을 적용하는 것이 코드도 간결하고 안전합니다.
만약 같은 타입 시그니처를 반복적으로 작성한 코드가 있다면 함수 타입을 분리해 내거나 이미 존재하는 타입을 찾아보도록 합니다. 라이브러리를 직접 만든다면 공통 콜백에 타입을 제공해야 합니다.
다른 함수의 시그니처와 동일한 타입을 가지는 새 함수를 작성하거나, 동일한 타입 시그니처를 가지는 여러 개의 함수를 작성할 때는 매개변수의 타입과 반환 타입을 반복해서 작성하지 말고 함수 전체의 타입 선언을 적용해야 합니다.
다른 함수의 시그니처를 참조하려면 typeof fn을 사용하면 됩니다.
// + - * /functionadd(a:number, b:number):number{return a + b;}functionsub(a:number, b:number):number{return a - b;}typeBinaryFn=(a:number, b:number)=>number;const add:BinaryFn=(a, b)=> a + b;const sub:BinaryFn=(a, b)=> a - b;
참고 의견
표현식, 함수가 나뉘는 이유는 JS this 바인딩 때문일것이고, 함수 오버로딩은 JS 스펙 상 불가능하니, 래퍼함수로써 사용할 것을 유도하는 것이 아닐까 싶습니다. JS의 수퍼셋으로써 동작을 해야하니, 윤희님이 원하시는 일반적인 오버로딩 구현은 불가하고, JS 세상에선 오버로딩 == 타입 래핑함수 거치기 라는 국룰이 있슴당.
인터페이스건 객체 타입이건, 정말로 양변하는 타입을 의도한 것이 아니면 항상 화살표 표기법을 사용하는 것이 좋다는 내용이 있기는 합니다.
인터섹션(&) 개념이 어렵고 D.TS의 정의가 길어집니다.
D.TS의 정의가 길어지는 것은 타입스크립트 컴파일러를 믿고 가야하지 않을까 싶습니다…ㅠ
인터섹션을 사용하지 않고 표현식으로 사용하는 방법을 고민해 보았습니다.
결론
오버로드가 단순히 input, output에 대한 interface 정의만 한다면 타입으로 정의하는 것이 더 좋지 않을까라고 생각을 했으나 오버로드를 위한 선언과 구현을 올바르게 한 것인가에 대해서는 그렇지 않습니다.
type으로 선언한 것도 선언한 것의 이름을 대변하는 이름을 붙인 것이고 구현 했다고 생각하는 것도 정의 된 것을 구현한 것이 아닌 익명함수를 넣고 그것과 타입이 같은 것일 뿐입니다.
연관된 값들이라면 엮여 있고 찾기 쉽게 끔 작성하는 것이 더 좋지 않을까라고 생각했지만 그것의 역할은 IDE의 역할입니다.
실제 작성할 때 힌트를 노출하는 것과 함수를 눌러서 구현을 찾아가는 것은 IDE가 지원해 주기 때문에 크게 걱정할 요소가 아닙니다.
일반적으로 널리 사용되는 방식을 사용하는 것이 좋습니다.
lib.dom.d.ts와 같은 일반 라이브러리나 많은 예시들에서 CASE 1을 사용합니다.
컴파일러가 알맞은 타입 검사를 하기 위해서, JavaScript와 비슷한 프로세스를 따릅니다. 오버로드 목록에서 첫 번째 오버로드를 진행하면서 제공된 매개변수를 사용하여 함수를 호출하려고 시도합니다. 만약 일치하게 된다면 해당 오버로드를 알맞은 오버로드로 선택하여 작업을 수행합니다. 이러한 이유로 가장 구체적인 것부터 오버로드 리스팅을 하는 것이 일반적입니다.
함수 표현식을 사용하는 것이 좋은 경우도 있지만 무조건적으로 사용하기 보다 상황에 맞게 사용해야 하는데 함수 오버로드의 개념으로 볼때는 표현식을 사용하는 것이 올바르지 않습니다.
추가적인 코드 개선
문제점
// 함수 선언(interface)functionadd(a:string, b:string):string;functionadd(a:number, b:number):number;// 함수 구현(implement)functionadd(a:any, b:any){return a + b;}// 함수 호출add('2','3');add(2,3);functionf(x:string|number){add(x, x);// No overload matches this call.}
아이템 50 : 오버로딩 타입보다는 조건부 타입을 사용하기
// 함수 선언(interface)functionadd<Textendsstring|number>(
a:T,
b:T,):Textendsstring?string:number;// 함수 구현(implement)functionadd(a:any, b:any){return a + b;}// 함수 호출add('2','3');add(2,3);functionf(x:string|number){add(x, x);}add('2',3);// Argument of type '3' is not assignable to parameter of type '"2"'.(2345)