
제네릭 문법은 타입의 이름을 붙이지 않고 다양한 타입에 대해 동작할 수 있는 포괄적인 코드를 작성할 수 있게끔 해준다.
function func(value:any) {
return value;
}
function func(value:string | number): string | number {
return value;
}
함수에 여러가지 타입을 넣고싶을 때 어떻게 하면 좋을까?? 위에 코드들은 문제가 발생한다.
any 타입은 모든 타입 검사를 무효화하며 any로 선언된 값은 어떤 타입으로도 할당 가능하고, 어떤 속성/메서드를 호출해도 컴파일 단계에서 에러가 나지 않는다.
또한 유니온 타입을 이용하면 타입 좁히기(타입가드,타입단언)을 해야만 메서드를 사용이 가능하다. 이러한 방법을 쓰지않고 제네릭을 사용하면 위의 문제점들을 해결이 가능하다.
function func<T>(value:T):T {
return value;
}
let str = func("asda");
let num = func(10);
이런식으로 호출할때 타입의 따라 타입이 변환된다. 제네릭을 사용하면 앞서 제네릭을 사용하지 않았을 때 발생했던 문제점을 말끔히 해결해준다.
동일한 기능을 하는 함수를 중복하여 생산할 필요도 없고, 타입 가드 및 타입 단언을 추가적으로 사용할 필요가 없다.
function swap<T>(a:T,b:T) {
return [b,a]
}
const [a,b] = swap("1",2);
만약 제네럴을 사용햇는데 swap 프로퍼티로 다른 값을 보내면 오류가 난다. 왜냐하면 T가 string 이엿는데 두번째 프로퍼티는 number이므로 첫번쨰 에서 이미 string으로 되었기 때문에 오류가 난다.
function swap<T,U>(a:T,b:U) {
return [b,a]
}
const [a,b] = swap("1",2);
이런식으로 <T,U> 로 바꿔주면 다른 타입을 받아 사용이 가능하다.
interface Lengthwise {
length: number;
}
// 제네릭 T 는 반드시 { length: number } 프로퍼티 타입을 포함해 있어야 한다.
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // 이제 .length 프로퍼티가 있는 것을 알기 때문에 더 이상 오류가 발생하지 않는다.
return arg;
}
map - 배열 각 요소에 콜백함수를 실행한 후 새로운 배열을 반환해줌
foreach - 배열 각 요소에 콜백함수 실행만 해줌, 반환값 X
function map(arr:unknown[], callback:(item: unknown)=> unknown ): unknown[]{
let result = [];
for(let i=0; i<arr.length; i++){
result.push(callback(arr[i]))
}
}
map함수는 배열과 콜백함수 두 가지를 인수로 받는다.
위는 두 가지 인수 모두 unknown, 반환값 역시 unknownd으로 임시 설정해놓은 상태.
map([1,2,3], (item) => item * 2);
이 경우 매개변수 arr와 map함수 반환값 타입은 number[ ] , 콜백함수의 인수와 반환값 모두 number타입이다. 그래서 위 함수 식을 제네릭 함수로 바꾸면
function map<T>(arr:T[], callback:(item: T)=> T): T[]{
let result = [];
for(let i=0; i<arr.length; i++){
result.push(callback(arr[i]))
}
}
하지만 이렇게 제네릭 타입 변수를 하나만 설정하면 문제가 생긴다.
📌만약에 map([1,2,3] , (it)=>it.toString()) 이라면?
매개변수 arr와 콜백함수 인수는 number, 반환값은 string 타입으로 사용되는 타입이 두 가지 이상이다.
그렇다면 타입 변수를 두 개 이상 써서 반환값의 타입을 분리해야 한다.
function map<T,U>(arr:T[], callback:(item: T)=> T): U[]{
let result = [];
for(let i=0; i<arr.length; i++){
result.push(callback(arr[i]))
}
}
📌또 만약에 이렇게 map 함수를 호출했다면?
map([100,"string"], (it)=> it.toString())
인수로 들어가는 배열 함수는 넘버타입과 스트링타입이 섞여있다.
이 때 map 함수의 타입 정의는 다음과 같다.
function map<string | number , string>(arr: (string | number)[], callback:(item: string | number) => string ) : string []
forEach 함수는 반환값은 없기때문에 매개변수로 들어가는 배열과 콜백함수의 매개변수 타입만 정리해주면 된다.
function forEach<T>(arr: T[], callback: (item:T) => void){
for(let i = 0; i < arr.length; i++){
callback(arr[i])
}
}
interface KeyPair<K,V> {
key : K;
value : V;
}
let keyPair: KeyPair<string,number> = {
key : "key",
value : 0,
}
interface Map<V> {
[key:string]:V;
}
let stringMap : Map<string> = {
key :"value",
}
let numberMap : Map<number> = {
key :10,
}
이렇게 인덱스 시그니처에 제네릭을 사용하면 하나의 타입으로 다양한
타입을 사용 가능하다.
interface Student {
type: "student";
school: String;
}
interface Developer {
type: "developer";
skill: string;
}
interface User<T> {
name: string;
profile: T;
}
let developerUser1: User<Developer> = {
name: "sono",
profile: {
type: "developer",
skill: "TS",
},
};
let studentUser1: User<Student> = {
name: "sono",
profile: {
type: "student",
school: "YC",
},
};
function goToSchool(user: User<Student>) {
const school = user.profile.school;
console.log(`${school}로 등교 완료!`);
}
위 코드와 같이 User 타입에서 profile 프로퍼티의 타입을 학생 또는 개발자로 한정짓지 않고 타입 변수로 정의해준다.
이렇게 하면 타입 가드를 통해 타입 좁히기를 할 필요없이, 매개변수로 들어오는 User의 Profile 타입이 Student라는 것을 명시할 수 있게 된다.
class List<T> {
construct(private List:T[]) {}
push(data:T){}
pop() {}
print() {}
}
const numberList = new List<number>([1,2,3]);
제네릭을 사용하면 다양한 타입을 가지는 클래스를 쉽게 만들 수 있다.
const promise = new Promise<number>((resole,reject)=> {
setTimeout(()=> {
resolve(20);
},3000);
reject("sadsadsa");
});
promise.then((response)=> {
console.log(response *10) // 여기서 unknown이라 타입어로
})
promise.catch((err)=> {
if(typeof err === "string") {
console.log(err)
}
})
function fetchNumber() : Promise<number> {
return new Promise((resole,reject)=> {
setTimeout(()=> {
resolve(20);
},3000);
reject("sadsadsa");
});
}
rresolve는 제네릭을 통해 타입을 설정할 수 있다. reject 자체가 any라서 타입 지정 불가하여 그 결과 catch에서 unknown이 된다.
비동기 함수 에서 타입을 정해줄 떄 비동기 함수의 반환 값을 그 타입을 정해주는 것을 기본적으로 사용한다.