한정된 다형성으로 인수의 개수 정의하기 가변 인수 함수 (= 임의의 개수의 인수를 받는 함수)에서도 한정된 다형성을 사용할 수 있습니다. 예를 들어 JavaScript의 내장 call 함수를 직접 구현하면, 아래처럼 정의하고 사용할 수 있습니다. call은 함수 하나와 임의 개수의 인수를 받아서 이 인수들을 함수에 건네 호출하는 함수입니다. function call( f: (...args: unknown[]) => unknown, ...args: unknown[] ): unknown { return f(...args) } function fill(length: number, value: string): string[] { return Array.from({length}, () => value) } cal..
한정된 다형성 이번엔 이진트리 예제를 사용합니다. 기본적으로 이진트리의 특징은 아래와 같습니다. 이진트리는 자료구조입니다. 이진트리는 노드를 갖습니다. 노드의 값을 가지며 최대 두 개의 자식 노드를 가리킬 수 있습니다. 노드는 잎 노드(leaf node: 자식이 없음) 또는 내부 노드(inner node: 적어도 한 개의 자식을 가짐) 둘 중 하나의 타입을 갖습니다. "T는 제네릭 타입이며, 이것은 T와 같은 타입이어야 한다"는 말로 표현할 수 없는 상황이 많습니다. 때론 U 타입은 적어도 T 타입을 포함하는 기능이 필요합니다. 이러한 상황을 U가 T의 상한 한계(upper bound)라고 설명합니다. 아래와 같은 세 종류의 노드를 갖는 이진트리를 구현한다고 해보겠습니다. 일반 TreeNode 자식을 갖..
제네릭 타입 별칭 간단한 예를 통해 타입 별칭에서 제네릭을 활용해 보겠습니다. click이나 mousedown 같은 DOM 이벤트를 설명하는 FirstEvent 타입을 정의해 보겠습니다. type FirstEvent = { target: T type: string } 타입 별칭에선 타입 별칭명과 할당 기호(=) 사이에만 제네릭 타입을 선언할 수 있습니다. FirstEvent의 target 프로퍼티는 , 등 이벤트가 발생한 요소를 가리킵니다. 예시로 버튼 이벤트는 아래처럼 표현할 수 있습니다. type FirstEvent = { target: T type: string } type ButtonEvent = FirstEvent FirstEvent 같은 제네릭 타입을 사용할 때는 타입이 자동적으로 추론되지 않..
제네릭 타입 추론 대부분의 상황에서 TypeScript는 제네릭 타입을 잘 추론해 냅니다. function map(array: T[], f: (item: T) => U): U[] { let result = [] for (let i = 0; i _ === 'a' // U 타입을 반환하는 함수 ) 위의 코드 map 함수를 함수 아래처럼 호출하면 TypeScript는 T를 string으로, U를 boolean으로 추론합니다. 그러나 제네릭도 명시적으로 지정할 수 있습니다. 제네릭 타입을 명시할 땐 모든 필요한 제네릭 타입을 명시하..
제네릭을 어디에 선언할 수 있을까? TypeScript에선 호출 시그니처를 정의하는 방법에 따라 제네릭을 추가하는 방법이 정해져 있습니다. type Filter = {(array: T[], f: (item: T) => boolean) =>T[]} // ① let filter: Filter = //... type Filter = { // ② (array: T[], f: (item:T) => boolean): T[] } let filter: Filter = // ... type Filter = (array: T[], f: (item: T) => boolean) => T[] // ③ let filter: Filter = // ... type Filter = (array: T[], f: (item: T) => ..
언제 제네릭 타입이 한정되는가? 제네릭 타입의 선언 위치에 따라 타입의 범위뿐 아니라 TypeScript가 제네릭 타입을 언제 구체 타입으로 한정하는지도 결정됩니다. type Filter = (array: T[], f: (item: T) => boolean) => T[] let filter: Filter = (array, f) => { const result = [] for(let i = 0; i < array.length; i++){ const item = array[i] if(f(item)) { result.push(item) } } return result } 위의 예시에서 를 호출 시그니처의 일부로 선언했으므로 TypeScript는 Filter 타입의 함수를 실제 호출할 때 구체 타입을 T로 한정..
다형성 모든 타입은 구체 타입(concrete type)입니다. boolean string Date[] {a: number} | {b: string} (numbers: number[]) => number 기대하는 타입을 정확하게 알고 있고, 실제 이 타입이 전달되었는지 확인할 때는 구체 타입이 유용합니다. 하지만 때론 어떤 타입을 사용할지 미리 알 수 없는 상황이 있는데, 이런 상황에선 함수를 특정 타입으로 제한하기 어렵습니다. JavaScript로 filter를 이용하여 배열을 반복하면서 정제하는 코드를 아래처럼 구현할 수 있습니다. function filter(array, f){ let result = [] for(let i = 0; i < array.length; i++) { let item = a..
오버로드된 함수 타입 호출 시그니처에서 사용한 함수 타입 문법(type Fn = (...) => ...)은 단축형 호출 시그니처(shorthand call signature)입니다. 해당 호출 시그니처를 더욱 명확하게 표현할 수 있습니다. 다시 Log를 예로 살펴보겠습니다. // 단축형 호출 시그니처 type Log1 = (message: string, userName?: string) => void // 전체 호출 시그니처 type Log2 = { (message: string, userName?: string): void } 위의 두 코드는 문법만 조금 다를 뿐 모든 면에서 같습니다. 위의 Log 함수처럼 간단한 상황이라면 단축형을 활용하되 더 복잡한 함수라면 전체 시그니처를 사용하는 것이 좋을 때도..