c#
이나 Java
등에서 사용되던 개념제네릭(generic)
이란 타입을 마치 함수의 파라미터처럼 유동적으로 사용하는것을 의미한다제네릭
은 일반적인 생성 시점에 타입을 명시하는 것이아닌, 생성 시점에 타입을 명시해 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이다아래 코드에서 매개변수 선언시
arr2
변수는 string
으로 이루어진 배열이므로 getSize
함수 파라미터에서 유니온타입으로 string[]
를 선언해줘야함arr3,arr4,arr5..
이런 배열들이 하나같이 타입이 다르다면?const getSize = (arr:number[]) : number => {
return arr.length;
}
const arr1 = [1,2,3];
getSize(arr1); //3
const arr2 = ['A','B','C']
getSize(arr2); //error
제네릭 함수 선언
<T>
는 타입 변수로써 함수 내부에서 임의의 타입으로 동작(arg: T):T
함수의 파라미터 부분과 리턴값도 타입변수로 받게 되는데, <T>
타입이 어떤 타입을 받아야하는지 작성해야한다function identity<T>(arg: T): T {
return arg;
}
제네릭 함수의 호출
output
변수는 string
이란 타입을 명시적으로 지정해주었으며, 해당 함수의 리턴값도 역시 string
타입이 될것이다.let output = identity<string>("myString");
let numberOutput = identity<number>(10);
강제성
identity
와 같은 함수를 만들때, 타입스크립트 컴파일러가 함수 본문에 입력된 매개변수를 올바르게 사용하도록 강제한다.function identity<Type>(arg: Type): Type {
return arg;
}
이렇게 쓰고 싶은 유혹을 받을 수도..
length
프로퍼티가 없는 타입을 대신 전달할 수도 있음을 타입스크립트가 알려주고 있다function loggingIdentity<Type>(arg: Type): Type {
/* Property 'length' does not exist on type 'Type'*/
console.log(arg.length);
return arg;
}
올바른 사용법은?
loggingIdentity
함수의 유형은 매개변수로 배열인 Type
을 받고 최종적으로 Type
배열을 반환하도록 작성해야한다Type
을 전체 타입이 아닌 작업 중인 타입의 일부로 사용할 수 있으므로 유연성이 향상된다function loggingIdentity<Type>(arg: Type[]): Type[] {
console.log(arg.length);
return arg;
}
/*배열의 타입선언은 Array로도 가능하니 이방법도 OK*/
function loggingIdentity<Type>(arg: Array<Type>): Array<Type> {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
loggingIdentity 함수
<T>
변수에 length
라는 속성이 존재하지 않으므로 타입에러가 나타나고 있다function loggingIdentity<Type>(arg: Type): Type {
console.log(arg.length);
// Error Property 'length' does not exist on type 'Type'.
return arg;
}
💡 해당 함수가 모든 조건에 동작하는 대신
.length
메서드가 존재하는 모든 유형에서만 작동하도록 제한하고 싶다면?
순서
extends
라는 키워드를 통해 타입변수를 확장시킨다interface Lengthwise {
length: number;
}
function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
// Now we know it has a .length property, so no more error
console.log(arg.length);
return arg;
}
다음은 제네릭을 사용한 함수 인터페이스의 예시이다
GenericIdentityFn
인터페이스는 <T>
는 타입변수이며, arg
파라미터와 반환값 또한 같은 타입이 들어오면 같은 타입으로 나가도록 한다identity
라는 실제 함수가 있으며, 이 함수는 어떠한 타입의 T
의 값을 받아 그대로 반환하는 로직이 있다myIdentity
변수에 identity
함수를 할당하는데 정의한 인터페이스를 명시적으로 선언하였다.myIdentity
변수가 GenericIdentityFn
인터페이스를 따르는 어떤 함수든 가질 수 있다고 명시하게 된다interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
특정타입을 지정해줘야하는 인터페이스 구현
<T>
를 인터페이스 이름 바로 뒷부분으로 옮겼다myIdentity
변수를 선언하고 해당 인터페이스의 타입을 적용할때 <number>
타입만 처리하는 GenericIndentityFN
함수라고 명시적으로 선언하였다.interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
위 두 코드의 차이점은?
<>
로 묶인 제네릭 타입 매개변수가 있다class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
GenericNumber
클래스를 문자 그대로 사용하면?
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function (x, y) {
return x + y;
};
console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));