아래와 같은 함수가 있다고 해보자
function printLog(text) {
return text;
}
파라미터로 text를 받는 함수 printLog는 반환값으로 text를 리턴하고 있다.
이 함수를 제네릭이 있을때와 없을때 코드를 비교해서 구현해보자!
function printLog(text: string): string {
return text;
}
printLog('hello'); // 정상
printLog(123); //에러
text에 string 외의 다른 값이 들어오면 컴파일 에러가 난다.
function printLog(text: string): string {
return text;
}
function printLogNumber(text: number): number {
return text;
}
printLog('hello'); // 정상
printLogNumber(123); //정상
위의 상황을 방지하기 위해서는 이렇게 각각 파라미터 text가 다른 타입으로 들어가는 함수를 만들면 된다.
그러나 이렇게 되면 코드가 반복적이고 유지보수에 좋아보이지 않는다.
그렇다면 유니온을 쓰는 방법은 어떨까?
function printLog(text: string | number) {
return text;
}
printLog('hello'); // 정상
printLogNumber(123); //정상
유니온은 들어가는 인수는 해결이 되지만, 함수 내에서 결국 string 과 number가 둘 다 접근할 수 있는 api
를 제공한다.
제네릭을 이용한다면?
function printLog<T>(text: T): T {
return text;
}
pringLog에 T라는 변수를 추가한 제네릭.
T는 유저가 준 파라미터의 타입을 캡처하고, 이 정보를 나중에 사용할 수 있게 한다.
const str = printLog<string>('hello');
함수를 호출할 때 인수 중 하나로 T를 string으로 명시하고 타입 주변을 <>로 감싸준다.
이 방법 말고도, 타입 추론 기능을 활용하면 아래와 같이 작성할 수 있다.
const str = printLog('hello');
하지만 위의 방법은 타입이 복잡해져서 컴파일러가 타입을 유추할 수 없게 되는 경우에는 사용할 수 없다.
interface Item<T> {
name: T;
stock: number;
selected: boolean;
}
위와 같은 제네릭 interface에는 name의 값으로 어떤 타입이 들어갈지 작성해주면, 인터페이스를 여러개 만들지 않고도 재사용할 수 있다.
const obj: Item<string> = {
name: "T-shirts",
stock: 2,
selected: false
};
const obj: Item<number> = {
name: 2044512,
stock: 2,
selected: false
};
function printLog<T>(text: T): T {
console.log(text.length);
return text;
}
위의 함수는 T에 string이 들어갈지, number가 들어갈지 모르기때문에,
text.length를 작성하게 되면 컴파일 에러가 난다.
function printLog<T>(text: T[]): T[] {
console.log(text.length);
return text;
}
위와 같이 제네릭에 타입을 주게되면, 유연하게 함수의 타입을 정의해줄 수 있다.
이 때 제네릭함수는 T라는 변수 타입을 받고, 인자 값으로는 배열 형태의 T를 받는다.
제네릭 타입은 배열이기 때문에, .length를 허용하게 된다.
function printLog<T>(text: Array<T>): Array<T> {
console.log(text.length);
return text;
}
이렇게 더욱 더 명시적으로도 사용 가능하다.