제네릭
제네릭은 타입을 미리 정의하지 않고 사용하는 시점에 원하는 타입을 정의해서 쓸 수 있는 문법이다.
마치 함수의 파라미터와 같은 역할을 한다. 여기에서 역할이란 함수의 인자에 넘긴 값을 함수의 파라미터로 받아 함수 내부에서 그대로 사용하는 방식을 의미한다.
function getText<T>(text: T): T {
return text;
}
getText<string>('hi');
<T>
라고 적는다.getText() 함수를 호출할 때 제네릭에 문자열 데이터 타입인 string 타입을 할당하면 제네릭 기본 문법 코드가 마치 다음과 같이 정의된 것 같은 효과가 생긴다.
function getText<string>(text: string): string {
ruturn text;
}
T 라고 선언한 모든 부분에 string 타입이 들어간다고 보면 된다. 마찬가지로 number, boolean, array, object 등 어느 타입이든 getText() 함수를 호출할 때 타입을 지정해서 사용할 수 있다.
함수의 역할과 동작은 같은데 타입이 다르기 때문에 함수를 분리해서 문자열 텍스트용 함수와 숫자 텍스트용 함수를 선언해 주면 결국 같은 동작을 하는 코드를 중복해서 선언한 꼴이다.
DRY(Don't Repeat Yourself) 라는 소프트웨어 개발 원칙이 있다. 중복 코드를 작성하지 말라는 의미이다.
any를 사용하면 타입스크립트의 장점들이 사라진다.
코드의 자동 완성이나 에러의 사전 방지 혜택을 받지 못한다.
제네릭을 쓰면 타입 때문에 불필요하게 중복되던 코드를 줄일 수 있고 타입이 정확하게 지정되면서 타입 스크립트의 이점을 모두 가져갈 수 있다.
interface ProductDroopdown {
value: string;
selected: boolean;
}
interface StockDroopdown {
value: string;
selected: boolean;
}
상품 목록과 상품의 재고를 보여주는 드롭다운 UI를 인터페이스로 정의한 코드이다.
이런 식으로 모든 데이터 타입을 일일이 정의한다면 타입 코드가 많아져서 관리도 어렵고 번거로운 작업이 된다.
interface Droopdown<T> {
value: T;
selected: boolean;
}
// 드롭다운 유형별로 하나의 제네릭 인터페이스를 연결
var product: Dropdown<string>;
var stock: Dropdown<number>;
var address: Dropdown<{ city: string; zipCode: string; }>;
인터페이스 이름 오른쪽에 <T>
를 붙여 주고 인터페이스 내부 속성 중 제네릭으로 받은 타입을 사용할 곳에 T를 연결한다.
이렇게 하면 타입을 유연하게 확장할 수 있을 뿐만 아니라 비슷한 역할을 하는 타입 코드를 대폭 줄일 수 있다.
타입 제약 문법은 모든 타입이 아니라 몇 개의 타입만 제네릭으로 받고 싶을 때 쓴다.
function embraceEverything<T extends string>(thing: T): T {
return thing;
}
제네릭을 선언하는 부분에 <T extends 타입>
과 같은 형태로 코드를 작성하면 string이 아닌 다른 타입을 제네릭으로 넘기려고 한다면 에러가 발생한다.
keyof는 특정 타입의 키 값을 추출해서 문자열 유니언 타입으로 변환해 준다.
type DeveloperKeys = keyof {name: string; skill: string;}
이 코드는 keyof 키워드를 사용하여 객체의 키를 DeveloperKeys라는 타입 별칭에 담아 둔다.
DeveloperKeys 타입에 마우스 커서를 올리면 다음과 같이 객체의 키가 유니언 타입으로 변환되어 있는 것을 볼 수 있다.
function printKeys<T extends keyof {name: string; skill: string;}>(value:T) {
console.log(value);
}
이 코드는 객체의 키 값만 인자로 받아 출력할 수 있도록 제네릭의 타입 제약을 걸어 놓은 함수이다.
제네릭을 정의하는 부분에 extends와 keyof를 조합해서 name과 skill 속성을 갖는 객체의 키만 타입으로 받겟다고 정의했다. 이 함수의 제네릭은 파라미터인 value에 연결되어 있기 때문에 함수를 호출할 때 넘길 수 있는 인자는 문자열 name과 skill 이다.
printKeys('address'); // error
인자로 넘긴 'address'는 문자열이라는 관점에서 'name'과 'skill'이라는 문자열과 데이터 타입이 같다.
하지만 문자열 타입 중에서도 'name'과 'skill'만 파라미터로 받겟다고 타입을 제약했기 때문에 이외의 문자열은 모두 인자로 사용할 수 없다. 이처럼 extends를 이용해서 제네릭의 타입을 제약할 때 keyof를 함께 사용하여 타입의 제약 조건을 까다롭게 만들 수 있다.