제네릭 기법
타입스크립트에서는 특별한 타입을 지정해줄 수 있는 제네릭을 사용할 수 있습니다.
정의한 타입을 지정한다는 점에는 자바의 제네릭과 비슷한 점이 있습니다.
타입스크립트에서는 any라는 모든 타입을 받을 수 있는 타입이 존재합니다.
그럼 any를 쓰면 되지 제네릭을 쓰는 이유가 무엇일까요?
any를 이용하여 구현하면 저장하고 있는 자료의 타입이 모두 같지 않다는 문제점이 생깁니다.
예를 들면 any 타입으로 구현한 배열에 문자, 숫자 등 여러 타입이 함께 들어갈 수 있다는 것이죠.
따라서 위의 스택에서 자료를 추출하는 경우 런타임에서 항상 타입 검사를 해줘야 한다는 문제가 있습니다.
다른 방법으로 타입을 상속해서 몇 가지 타입만 허용하는 방법으로 구현을 할 수 있습니다.
하지만 코드랑이 길어지고 가독성이 떨어질 수 있기에 제네릭을 사용하는 것입니다.
제네릭 구현 문법 - 함수일 경우
function pipeOne(value: any): any {
return value;
}
function pipeTwo<T>(value: T): T {
return value;
}
let p1 = pipeOne(10);
let p2 = pipeTwo('10');
let p3 = pipeTwo(true);
console.log(p1); // 10
console.log(p2); // 10
console.log(p3); // true
함수로 제네릭을 구현할 경우 함수명 뒤에 <T>를 붙여주고 파라미터, 리턴타입에 T를 기술하면 됩니다.
<T>를 선언하면 T를 타입 변수 취급하며, 함수 내에서 타입 변수로써 사용할 수 있습니다.
type User = {
id: number;
name: string;
}
const pipeObjectOne = <T>(obj: T): T => {
return obj;
}
let po1 = pipeObjectOne({id: 1, name: '김', zipcode: 50213 });
let po2 = pipeObjectOne<User>({id: 1, name: '김', zipcode: 50213 });
console.log(po1);
console.log(po2);
제네릭 구현 문법 - 클래스일 경우
class Stack<T> {
private data: T[] = [];
constructor() {}
push(item: T): void {
this.data.push(item);
}
pop(): T {
return this.data.pop();
}
}
const numberStack = new Stack<number>();
const stringStack = new Stack<string>();
numberStack.push(1);
stringStack.push('a');
각 스택은 항상 생성할 때 선언한 타입만을 저장하고 리턴합니다.
이렇게 하면 컴파일러가 리턴하는 타입을 알 수 있게 되므로 에디터에서 자동 완성을 사용할 수 있게 되므로 생산성 향상에도 기여한다는 장점이 있습니다.
제네릭 구현 예시 - 두 개 이상의 타입 변수(함수)
function toPair<T, U>(a: T, b: U): [ T, U ] {
return [ a, b ];
}
toPair<string, number>('1', 1); // [ '1', 1 ]
제네릭 구현 예시 - 두 개 이상의 타입 변수(클래스)
type Address {
zipcode: number;
address: string;
}
class State<S, Config={}> {
private _state: S;
config: Config;
constructor(state: S, config: Config) {
this._state = state;
this.config = config;
}
getState(): S {
return this._state;
}
}
let s1 = new State<Address, { active: boolean }>({
zipcode: 50213,
address: '서울시',
}, {
active: true
});
const s1Data = s1.getState();
// 타입 추론 가능
console.log(s1Data.zipcode, s1Data.address, s1.config.active);
제네릭 확장(심화)
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
console.log(getProperty(x, "a"));
console.log(getProperty(x, "m")); // 에러가 발생함. 파라미터로 넘긴 객체에 없는 key임
객체를 파라미터를 넘기고 그 객체 안에 있는 특정 value 값을 가져오는 예제입니다.
extends 지시어로 상속을 하는데, keyof 지시어로 제네릭 타입 Type의 key값만 상속할 수 있습니다.
즉, Key 제네릭 타입은 Type 제네릭 타입의 key값으로 이루어진 타입이됩니다.
이러한 방법으로 객체 내에 key을 체크하여 value를 반환하는 기능을 구현할 수 있습니다.
interface KeyPair<T, U> {
key: T;
value: U;
}
let kv1: KeyPair<number, string> = { key: 1, value: 'kim'};
let kv2: KeyPair<number, number> = { key: 2, value: 12345};
인터페이스에 제네릭으로 타입을 지정함으로써, 생성할 객체의 유효성검사를 할 수 있습니다.
class Stack<T> {
private data: T[] = [];
constructor() {}
push(item: T): void {
this.data.push(item);
}
pop(): T {
return this.data.pop();
}
}
function getFirst<T extends Stack<U>, U>(container: T): U {
const item = container.pop();
container.push(item);
return item;
}
getFirst<Stack<number>, number)(numberStack);
getFirst<number, number>(1); // Type 'number' does not satisfy the constraint 'Stack<number>'
타입 변수는 기존에 사용하고 있는 타입을 상속할 수도 있습니다.
이 점을 이용하면 입력 받을 변수의 타입을 제한할 수 있습니다.
https://hyunseob.github.io/2017/01/14/typescript-generic/
'typeScript' 카테고리의 다른 글
typeScript - @types (0) | 2021.08.01 |
---|---|
typeScript - 타입가드 (0) | 2021.08.01 |
typeScript - 튜플 (0) | 2021.07.26 |
typeScript - typeScript 개괄3(기타) (0) | 2021.07.07 |
typeScript - typeScript 개괄2(객체 생성과 접근자 제한) (0) | 2021.07.07 |
댓글