제네릭
타입을 마치 함수의 파라미터 개념인 것처럼 받게 되는 것이다.
C#, Java 등의 언어에서 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징이자 문법이다. 특히, 한가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성하는데 사용한다.
제네릭의 기본 문법
function logText<T>(text: T):T {
console.log(text);
return text;
}
logText('hi');
기본적으로 파라미터(text)의 타입은 'hi'가 된다.
문자열을 그대로 들고가서 한 바퀴(console.log(), return) 돈 뒤에 반환하는 것까지도 문자열이 된다고 정의할 수 있다.
즉, 호출하는 시점에 문자열, 숫자 등의 타입을 넘겨줄 수 있는 것이 제네릭이다.
logText<string>('hi');
명시적으로 string이라는 타입을 넘겨줬다.
따라서, logText()의 안에서 처리하는 text에 대한 타입은 위에서 넘겼던 문자열(string)이 된다.
기존 타입 정의 방식과 제네릭의 차이점
1. 함수 중복 선언의 단점
function logText(text) {
console.log(text);
return text;
}
logText('a');
logText(10);
logText(true);
문자열, 숫자, 를 넣었을 때 전부 다 받을 수 있는 이유는 현재 logText()함수에서 타입을 정의하지 않아 암묵적으로 타입이 any라고 되어있기 때문에 어떤 타입도 받을 수 있는 것이다.
function logText(text: string) {
console.log(text);
// text.split('').reverse().join(''); // 타입이 string일때 가능.
return text;
}
function logNumber(num: number) {
console.log(num);
return num;
}
logText('a');
logText(10);
logNumber(10);
logText(true);
위의 경우처럼 타입을 다르게 받기 위해 중복되는 코드들을 반복해서 사용하는 것은 코드관점, 유지보수 관점에서 좋지 않다.
이런 것들을 해결하기 위해 유니온 타입을 사용할 수 있다.
2. 유니온 타입을 이용한 선언 방식의 문제점
아래의 코드는 유니온 타입을 사용해 string타입과 number타입 모두 가능하도록 해준 상태이다.
function logText(text: string | number) {
console.log(text);
return text;
}
logText('a');
logText(10);
const a = logText('a');
a.split('') // split에 빨간 밑줄 에러가 발생한다.
string타입과 number타입 모두 적어줬지만, 문자열을 넣어줘도 string에서 제공하는 split이 에러가 난다.
타입이 string | number에서는 string을 제공하지 않기 때문이다. 즉, 정확한 타입을 넣어줘야만 사용가능하다는 문제점이 생기는 것이다.
인풋값은 해결되었지만, 반환값은 해결되지 않아서 문제가 발생하는 것이다.
제네릭의 장점과 타입 추론에서의 이점
제네릭은 타입 정의에 대한 이점을 가지고 있다. 함수를 정의할 때 타입을 비워놓고, 해당 함수를 호출한 시점에 타입을 정의하는 것이 제네릭이다.
제네릭을 사용하면 타입을 추론을 해서 최종 반환값까지 정의할 수 있다.
function logText<T>(text : T): T {
console.log(text);
return text;
}
string이라는 타입을 받아서 쓰겠다고 호출할 때 정의하는 것이다.
파라미터(인자)와 반환값이 모두 string에 될 것이라고 타입스크립트 내부에서 제네릭을 이용해서 선언한 것이다.
const str = logText<string>('abc');
str.split(''); // string에 사용하는 split을 사용할 수 있다.
// 위의 함수의 같은 함수인 logText함수를 호출시 boolean을 타입으로 정의했다.
const login = logText<boolean>(true);
인터페이스에 제네릭을 선언하는 방법
// 인터페이스에 제네릭을 선언하는 방법
interface Dropdown {
value: string;
selected: boolean;
}
const obj: Dropdown = { value: 'abc', selected: false };
const obj: Dropdown = { value: 10, selected: false }; // value값에는 string이라는 타입이 정의되어있기 때문에 에러가 발생한다.
Dropdown인터페이스의 value를 string라고 정의했기 때문에 value에 숫자를 사용할 경우 에러가 발생하게 된다.
이처럼 여러 타입을 사용하기 위해 인터페이스 선언이 늘어나게 되고 코드가 지저분해질 경우, 제네릭을 사용해 손쉽게 선언할 수 있다.
interface Dropdown<T> {
value: T;
selected: boolean;
}
const obj:Dropdown<string> = { value: 'abc', selected: false };
const obj:Dropdown<number> = { value: 10, selected: false };
제네릭을 이용하면 여러 타입의 인터페이스를 손쉽게 사용할 수 있다.
Dropdown이라는 인터페이스를으로 제네릭으로 정의하는데, 정의를 할 때 타입을 선언하는 시점에 타입을 추가적으로 넘겨서 Dropdown인터페이스의 타입을 바꿔준다.