💡 유연하고! 타입이 보장되고, 재사용성이 높은 제네릭(Generics)!!
선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법
C#
과 Java
같은 언어에서, 재사용성이 높은 컴포넌트를 만들 때 자주 활용하는 제네릭(Generics)!!!// text라는 파라미터에 값을 넘겨 받아 text를 반환하는 함수
const getText = (text) => {
return text;
}
// 파라미터에 어떤 값을 넘겨주더라도 그대로 반환된다.
getText('april'); // 'april'
getText(20); // 10
getText(true); // true
// 제네릭 기본 문법이 적용된 함수
const getText = <T>(text: T): T => {
return text;
}
// 함수를 호출할 때, 함수 안에서 사용할 타입을 넘겨줄 수 있다
getText<string>('april'); // 'april'
getText<number>(20); // 10
getText<boolean>(true); // true
// 두 개의 제네릭이 적용된 함수
const getUserInfo = <T, K>(name: T, age: K): [T, K] => {
return [name, age];
};
// 함수를 호출할 때, 함수 안에서 사용할 타입을 넘겨줄 수 있다
getUserInfo('april', 20);
파라미터의 타입이 여러 종류일 때 any
를 사용하게 되는데,
any
타입은 타입 검사를 하지 않기 때문에 문제가 생길 수 있다
const getText = (text: any): any => {
return text;
}
먼저 함수의 이름 바로 뒤에 <T>
라는 코드를 추가하고 함수의 인자와 반환 값에 모두 T
라는 타입을 추가하면,
함수를 호출할 때 넘긴 타입에 대해 타입스크립트가 추정할 수 있게 된다.
💡 따라서, 함수의 입력 값에 대한 타입과 출력 값에 대한 타입이 동일한지 검증할 수 있게 된다.
const getText = <T>(text: T): T => {
return text;
}
배열을 생성하는 함수로 익히는, 제네릭 예제 코드
숫자 배열을 생성하는 함수와 문자 배열을 생성하는 함수 😳
중복된 코드가 많다... 😬😮💨
const makeNumberArray = (defaultValue: number, size: number): number[] => {
const arr: number[] = [];
for (let i = 0; i < size; i++) {
arr.push(defaultValue);
}
return arr;
};
const makeStringArray = (defaultValue: string, size: number): string[] => {
const arr: string[] = [];
for (let i = 0; i < size; i++) {
arr.push(defaultValue);
}
return arr;
};
const arr1 = makeNumberArray(1, 10);
const arr2 = makeStringArray('empty', 10);
const makeArray = <T>(defaultValue: T, size: number): T[] => {
const arr: T[] = [];
for (let i = 0; i < size; i++) {
arr.push(defaultValue);
}
return arr;
};
const arr1 = makeArray<number>(1, 10);
const arr2 = makeArray<string>('empty', 5);
// makeArray 함수의 첫 번째 파라미터를 알면 타입 T도 알 수 있기 때문에,
// 호출 시 타입 T의 정보를 명시적으로 전달하지 않아도 된다.
const arr3 = makeArray(1, 10);
const arr4 = makeArray(1, 10);
extends
키워드로 제네릭 타입 제한하기리액트와 같은 라이브러리의 API는 입력 가능한 값의 범위를 제한한다.
예를 들어, 리액트의 속성값 전체는 객체 타입만 허용된다.
이를 위해 타입스크립트의 제네릭은 타입의 종류를 제한할 수 있는 기능을 제공한다.
// 제네릭 T 타입을 number | string에 할당 가능한 타입으로 제한
const identity = <T extends number>(p1: T): T => {
return p1;
};
identity(1);
identity('a');
identity([]); // 타입 에러
// 제네릭 T 타입을 number | string에 할당 가능한 타입으로 제한
const identity = <T extends number>(p1: T): T => {
return p1;
};
identity(1);
identity('a');
identity([]); // 타입 에러
interface IPerson {
name: string;
age: number;
}
interface IKorean extends IPerson {
liveInSeoul: boolean;
}
// 제네릭 T는 IPerson에 할당 가능한 타입이어야 한다
// 제네릭 K는 IPerson의 속성 이름이어야 한다.
// 참고로 keyof는 인터페이스의 모든 속성 이름을 유니온 타입으로 만들어 준다.
const swapProperty = <T extends IPerson, K extends keyof IPerson>(
p1: T,
p2: T,
name: K
): void => {
const temp = p1[name];
p1[name] = p2[name];
p2[name] = temp;
};
const p1: IKorean = {
name: 'april',
age: 20,
liveInSeoul: true,
};
const p2: IKorean = {
name: 'yrkim',
age: 20,
liveInSeoul: true,
};
swapProperty(p1, p2, 'age'); // p1, p2는 IPerson에 할당 가능하기 때문에 타입에러가 발생하지 않는다