<타입> 으로 지정하고 싶은 타입을 지정해 길어지는 타입선언을 하나의 변수로 사용
function toObj<T>(a: T, b: T): { a: T, b: T} {
return {a, b}
}
toObj<string>('A', 'B')
toObj<number>(1, 2)
toObj<boolean>(true, false)
toObj(null, null)
T는 타입변수를 의미하고 함수를 호출할때 앞에 붙이는 <> 안의 타입이 들어가게 된다.
함수 호출 시 타입 추론 때문에 <> 안에 타입을 지정해주지 않아도 첫번째 들어간 인수로 타입 T가 정해지기 때문에 두번째 인수로 다른 타입이 들어올 경우 에러를 낸다.
하지만 지금 상태에서는 여러가지 타입에 대해서 허용을 하는데 만약 특정 타입만 허용하고 싶으면 제약조건을 사용해야 한다.
function toObj<T extends string | number | boolean>(a: T, b: T): { a: T, b: T) {
return {a, b}
}
toObj(null, null) // 에러
extends를 통해 타입을 string, number, boolean 타입만 허용할 수 있다.
extends를 기준으로 왼쪽 오른쪽 타입이 같으면 true를 반환한다.
interface ToObj<T> {
a: T
b: T
}
function toObj<T extends string | number | boolean>(a: T, b: T): ToObj<T> {
return {a, b}
}
인터페이스에서의 제네릭을 이용할 수도 있다.
interface User<T, U,, V> {
name: T,
age: U,
isValid: V
}
type U = User<string, number, boolean>
const hunoh: U = { name: 'Hunoh', age: 11, isValid: true}
만약, 위와 같이 객체 형태의 값 말고 배열 형태의 값도 받고 싶다면 타입을 사용하는 것이 적합하다.
type User<T, U, V> = { name: T, age: U, isValid: V } | [T, U, V]
type U = User<string, number, boolean>
const hunoh: U = { name: 'Hunoh', age: 11, isValid: true}
const ohhun = U = ['Hunoh', 11, true]
class Basket<T> {
public items: T[]
constructor(...rest: T[]) {
this.items = rest
}
putItem(item: T) {
this.items.unshift(item)
}
removeItem(count: number) {
return this.items.splice(0, count)
}
}
const baskets = new Basket('Apple', 'Banana', 'Cherry')
baskets.putItem('Orange')
const fruit = basekts.removeItem(2)
const baskets2 = new Basket(100, 1000)
baskets.putItem(5000)
const number = baskets2.removeItem(2)
인스턴스를 생성할때 명시적으로 타입을 지정하지 않은 경우 타입추론에 의해 생성자 함수에서 처음으로 string을 타입으로 지정하게 되고, 명시적으로 작성하는 경우 바로 string을 타입으로 지정하게 된다는 차이점이 있다.
따라서 호출할때 타입을 명시하든 타입 추론을 통해 타입을 추론하든 동적으로 제너릭을 통해 문자 배열과 숫자 배열을 생성하여 조작할 수 있다.
class Basket<T extends string> {}
위에서 배운 제약조건을 통해 다른 타입은 안되고 문자로만 이루어진 배열을 만드려면 extends를 사용하면 된다. 하지만 이렇게만 할 경우 putItem 함수에서 오류가 생기는데 처음 인스턴스를 생성할 때 T를 최대한 구체적으로 타입을 정하기 때문에 Apple | Banana | Cherry이런 식으로 지정하게 된다. 따라서 Orange는 들어갈 수 없게 된다.
이는 타입추론의 문제점이라고 생각하면 된다. 따라서 인스턴스를 생성할 때 명시적으로 타입을 지정해주면 된다.
const baskets = new Basket<string>('Apple', 'Banana', 'Cherry')
삼항연산자를 통해 타입을 지정할 수 있다. 조건부 타입을 사용하는 경우는 자주는 없다고 한다.
type MyType<T> = T extends string | number ? boolean : never
const a: MyType<string> = true
const b: MyType<number> = true
const c: MyType<null> = true // 에러
string이나 number타입만 인수로 들어갈 수 있고 return은 boolean 타입이다.
type Myexclude<T, U> = T extends U ? never : T
type MyUnion = string | number | boolean | null
const a: MyExclude<MyUnion, boolean | null> = 123
const a: MyExclude<MyUnion, boolean | null> = false // 에러
string과 number타입만 타입 지정이 가능하고, 그 외의 boolean과 null 타입이 들어오는 경우는 never를 지정해줘서 에러를 내게 한다.
type MyUnion = string | number | boolean | null
const a: Exclude<MyUnion, boolean | null> = 123
const a: Exclude<MyUnion, boolean | null> = false // 에러
사실 Exclude이라는 내장된 유틸리티 타입을 통해 사용이 가능하다.
Infer를 통해 타입이 추론이 가능하면 그 타입을 사용한다는 뜻이다.
보통 구조가 같으면 타입 추론이 가능하다.
typs ArrayItemType<T> = T extends (Infer I)[] ? I : never
const numbers = [1, 2, 3]
const a: ArrayItemType<typeof numbers> = 123
const b: ArrayItemType<boolean> = 123 // 에러
이 또한 ReturnType이라는 내장 유틸리티 기능이 타입이 있다.
const a: ReturnType<typeof numbers> = 123