🔍 글을 시작하기 전에
해당 글은 타입스크립트 프로그래밍 책과 유데미 타입스크립트 강의를 공부하여 정리한 글입니다.
수정해야 할 부분이 있다면 언제든지 알려주세요!
T extends type
전달받은 두 개의 객체를 하나로 합치는 함수를 구현해 보자.
function merge<T, U>(objA: T, objB: U) {
return Object.assign(objA, objB);
}
코드 실행은 잘 되지만, 에러가 발생하는 것을 확인할 수 있었다.
에러는 return 문에서 assign
메서드에 전달되는 objA
에서 발생했는데, 다음과 같은 오류가 발생했다.
(더 보기를 누르면 발생한 오류 문구를 확인할 수 있어요.)
No overload matches this call.
Overload 1 of 4, '(target: {}, source: U): {} & U', gave the following error. Argument of type 'T' is not assignable to parameter of type '{}'.
Overload 2 of 4, '(target: object, ...sources: any[]): any', gave the following error. Argument of type 'T' is not assignable to parameter of type 'object'.
이는 Object.assign()
함수에서 첫 번째 매개변수로 전달되는 objA
가 object 타입이 아니라서 발생하는 오류였다.
오류를 해결하기 위해서는 첫 번째 매개변수인 objA 타입에 object가 온다는 것을 명시해줘야 했다.
extends
키워드를 사용하여 오류를 해결했다.
function merge<T extends object, U extends object>(objA: T, objB: U) {
return Object.assign(objA, objB);
}
let objMerge = merge({name: 'Jae'}, {age: 27});
console.log(objMerge); // {"name": "Jae", "age": 27}
T extends object
를 사용함으로써 T 타입이 어떤 구조를 가지는 객체인지는 상관없이,
일단 타입이 객체여야 한다는 의미를 가지게 된다.
하나의 제한을 적용한 한정된 제네릭
U타입은 적어도 T타입을 포함하고 있다는 것을 U가 T의 상한 한계다라고 한다.
type TreeNode = {
value: string
}
type LeafNode = TreeNode & {
isLeaf: true
}
type InnerNode = TreeNode & {
children: [TreeNode] | [TreeNode, TreeNode]
}
let a: TreeNode = {value: 'a'};
let b: LeafNode = {value: 'b', isLeaf: true};
let c: InnerNode = {value: 'c', children: [b]};
이런 이진트리가 있을 때, node를 인수로 받아 value에 매핑 함수를 적용해서 새로운 node를 반환하는
mapNode
함수를 구현한다고 했을 때, mapNode
함수는 다음과 같이 작성할 수 있다.
function mapNode<T extends TreeNode>(node: T, f: (value: string) => string): T {
return {
...node,
value: f(node.value)
}
}
그리고 LeafNode
타입인 b
를 매핑 함수 인자에 전달한 뒤 타입을 확인해 보자.
let b1 = mapNode(b, _ => _.toUpperCase()); // { "value": "B", "isLeaf": true }
// b1의 타입: LeafNode
b1
에 마우스를 올려보면 타입 정보가 보존되어 여전히 LeafNode
타입이다.
만약 T extends TreeNode
를 지우고 T라고 작성하면 어떻게 될까?
function mapNode<T>(node: T, f: (value: string) => string): T {
return {
...node,
value: f(node.value) //Error: Property 'value' does not exist on type 'T'.
}
}
T에 value 속성이 없다는 오류가 발생하게 된다.
T 타입에 상한 경계가 없기 때문에 node.value
를 읽으려고 할 때 안전하지 않아 오류가 발생하게 되는 것이다.
그렇다면 제네릭을 사용하지 않고 T대신 그냥 TreeNode
를 사용한다면 어떻게 될까?
function mapNode(node: TreeNode, f: (value: string) => string): TreeNode {
return {
...node,
value: f(node.value)
}
}
let b1 = mapNode(b, _ => _.toUpperCase()); // { "value": "B", "isLeaf": true }
// b1의 타입: TreeNode
b1
의 값은 동일하게 반환되지만, b1
의 타입이 TreeNode
로 바뀌게 된다.
즉, b
노드가 매핑 함수를 통해 매핑되면서 타입 정보가 날아가게 되는 것이다.
T extends TreeNode
로 타입을 표기함으로써 매핑한 이후에도 사용된 노트의 특정 타입을 보존할 수 있게 된다.
타입 별칭에서 제네릭 활용하기
타입 별칭에서 제네릭을 활용할 경우 타입이 자동으로 추론되지 않기 때문에 타입 매개변수를 명시적으로 한정해야 한다.
type MyEvent<T> = {
target: T
type: string
}
let myEvent: MyEvent<HTMLButtonElement | null> = {
target: document.querySelector('#myButton'),
type: 'click'
}
'Front-End > TypeScript' 카테고리의 다른 글
타입스크립트[TypeScript] | 인터페이스 vs Type alias (0) | 2023.04.26 |
---|---|
타입스크립트[TypeScript] | 4장 연습 문제 (0) | 2023.04.24 |
타입스크립트[TypeScript] | 제네릭(Generic)이란? + filter, map 함수 구현해보기 (0) | 2023.04.19 |
타입스크립트[TypeScript] | 클래스 상속(extend), 추상 클래스(abstrct), 정적 메서드(static) (1) | 2023.04.18 |