제네릭이란 다양한 타입에서도 동작하는 컴포넌트를 의미한다.
any 대신에 Type을 사용하면 타입 추론이 가능하고 확장이 가능한 구조로 만들 수 있다.
<>꺽쇠 안에 타입 이름을 적어주면 되는데 보통 <T>를 사용한다.(이 글에서는 <Type>을 사용)
/*
인자로 받은 타입을 재사용할 수 없음
반환할 타입을 인자로 받은 타입(arg)과 똑같이 지정할 수가 없음!
*/
function identity(arg: any): any {
return arg;
}
// 인자로 받은 타입을 그대로 반환하여 다시 사용할 수 있음
function identity<Type>(arg: Type): Type {
return arg;
}
function identity<Type>(arg: Type): Type {
return arg;
}
// 명시적인 타입 인수 전달
let explicitOutput = identity<string>("myString");
/*
타입 인수 추론
컴파일러가 알아서 인자로 받은 타입을 추론함
*/
let typeArgumentInference = identity("myString");
// 제네릭 함수
function identity<Type>(arg: Type): Type {
return arg;
}
/*
제네릭 함수 타입
타입이 함수 형식이고 이 형식에 맞는 함수를 이 변수에 넣어줄 수 있음
*/
let myIdentity: <Type>(arg: Type) => Type = identity;
// 반환 타입이 다른 함수를 myIdentity 변수에 넣어보는 예시
function notIdentityFunc<Type>(arg: Type): string {
return "hi";
}
let myIdentity2: <Input>(arg: Input) => Input;
myIdentity2 = notIdentityFunc; // 당연히 에러남 : Type '<Type>(arg: Type) => string' is not assignable to type '<Input>(arg: Input) => Input'.
// Type 'string' is not assignable to type 'Input'.
// 'Input' could be instantiated with an arbitrary type which could be unrelated to 'string'.
/*
제네릭 함수 타입을 호출 시그니처(call signature)로 사용하는 방법
이 예시에서는 위의 myIdentity와 큰 차이가 없음
이렇게 할거면 제네릭 인터페이스를 사용하는 것이 유리함
*/
let myIdentity3: { <Type>(arg: Type): Type } = identity;
호출 시그니처란 함수의 인자와 반환 타입을 미리 선언하는 것을 말하는데, 다형성과 관련이 있다. 호출 시그니처를 다르게 만들어두면, 다른 타입이 함수의 인자로 들어올 때 다르게 처리할 수 있다. 그런데 호출 시그니처를 제네릭 타입으로 사용하면 다양한 타입에 같은 처리를 하도록 구현할 수 있다.
// 제네릭 함수를 인터페이스로 빼서 재사용
interface GenericIdentityFn {
<Type>(arg: Type): Type;
}
function identity<Type>(arg: Type): Type {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
타입으로 선언하는 대신 인터페이스를 사용하는 이유는 인터페이스는 extends를 통해 확장이 가능하기 때문이다. 그리고 인터페이스는 같은 이름으로 다시 선언을 하면 자동으로 확장이 된다.
// 클래스의 정적(static) 속성을 제네릭으로 사용해보는 예시
class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
}
// 에러 : Property 'zeroValue' has no initializer and is not definitely assigned in the constructor.
// 'add'도 같은 에러남
// 클래스의 instance를 생성해서 사용하면 에러가 나지 않음
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function (x, y) {
return x + y;
};
console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));
클래스에서는 제네릭 타입을 static 속성에서는 사용할 수 없고 instance의 속성에서는 사용할 수 있다.
타입스크립트 핸드북
[Typescript] Call signature, Overloading, Generic
타입스크립트 type과 interface의 공통점과 차이점