재사용 가능한 컴포넌트를 구축하는 것은 소프트웨어 엔지니어링에서 중요한 부분이다. 현재의 데이터와 미래의 데이터를 모두 다룰 수 있는 컴포넌트
는 소프트웨어 시스템을 구성하는데 있어서 가장 유연한 능력을 제공
할 것이다.
아래의 예제를 매개변수를 그대로 반환하는 함수이다.
function numberReturnFunc(arg: number): number {
return arg;
}
function stringReturnFunc(arg: string): string {
return arg;
}
function booleanReturnFunc(arg: boolean): boolean {
return arg;
}
함수의 기능은 같지만 반환하는 타입이 다르기 때문에 함수를 여러개로 구현했다. 이런 방식은 제네릭을 사용해서 하나의 함수로 구현이 가능하다.
function genericReturnFunc<T>(arg: T): T {
return arg;
}
제네릭 함수 구현 방법은 함수명 뒤에 <T>
를 추가하고, T를 매개변수의 타입과 반환 타입으로 설정할 수 있다. (Type 의 약자로 T를 많이 사용한다.)
제네릭 함수를 호출하는 방법이다. <>안에 인수의 타입을 작성하여 호출
하며 인수와 인수의 타입이 다르면 경고문이 발생한다.
// 정상
let num = genericReturnFunc<number>(123);
let str = genericReturnFunc<string>('abc');
// 오류: Argument of type 'string' is not assignable to parameter of type 'number'
let str = genericReturnFunc<number>('abc');
any
타입으로 매개변수의 타입과 함수의 반환 타입을 지정할 수 있다. any
타입은 어떤 타입이든 받을 수 있다는 점에서 제네릭 이지만, 실제 함수가 반환할 때 어떤 타입인지 알 수 없다.
function anyReturnFunc(arg: any): any {
return arg;
}
// number 타입인 123을 전달했지만, 어떤 타입을 return 하는지 알 수 없다.
let num = anyReturnFunc(123);
// string 타입인 123을 전달했지만, 어떤 타입을 return 하는지 알 수 없다.
let str = anyReturnFunc('abc');
따라서 제네릭 함수를 사용해서 어떤 타입을 반환하는 알 수 있게 작성하는데 유지보수 측면에서 상당히 좋다.
function genericReturnFunc<T>(arg: T): T {
return arg;
}
let num = genericReturnFunc<number>(123);
제네릭 함수를 호출할 때 타입 인수 추론
을 사용하는 방법이 존재한다. 전달하는 인수를 컴파일러가 추론해서 T
값을 자동으로 정하게 된다. 하지만 코드를 간결하게 해주는 장점이 있지만, 타입을 유추하기 어려운 경우에는 <>
안에 타입을 명시하는 것이 좋다.
let str = genericReturnFunc('abc');
매개변수의 length를 반환하는 코드이다.
any 타입은 매개변수의 프로퍼티를 체크하지 않아 오류가 발생하지 않는다.
// any
function anyReturnFunc(arg: any): any {
return arg.length;
}
제네릭 함수는 어떤 타입이 올지 모르기 때문에 length 프로퍼티를 사용할 수 없다.
// generic
function genericReturnFunc<T>(arg: T): T {
return arg.length
}
제네릭 클래는 클래스 이름 뒤 <>
안에 타입을 작성한다.
class GenericClass<T> {
value: T;
add: (x: T, y: T) => T;
}
let num = new GenericClass<number>();
num.value = 0;
num.add = function(x, y) { return x + y }
GenericClass
는 number
타입 이외의 타입도 사용할 수 있다.
let num = new GenericClass<string>();
num.value = '';
num.add = function(x, y) { return x + y }
제약 조건을 명시하는 인터페이스를 만들고 제약 사항을 extends 키워드로 연결한다.
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
// length 프로퍼티가 있다는 것을 알기 때문에 오류가 발생하지 않는다.
console.log(arg.length);
return arg;
}
제네릭 함수는 제한되어 있기 때문에 모든 타입에 동작하지 않고 length 프로퍼티가 있는 값을 전달해야 한다.
// string은 length 프로퍼티가 존재하므로 에러가 발생하지 않는다.
loggingIdentity('abc');
// number는 length 프로퍼티가 존재하지 않아 에러가 발생한다.
loggingIdentity(123);