제네릭이란
타입을 매개변수화하여 코드의 재사용성을 높이고 타입 안정성을 강화하는 기능이다.
사용방법
함수
함수 이름 뒤에 꺾쇠 괄호(<>) 안에 제네릭 타입 매개변수를 넣어 정의한다.
function identity<T>(arg: T): T {
return arg;
}
const output1 = identity<string>("myString");
const output2 = identity<number>(100);
인터페이스
인터페이스 이름 뒤에 제네릭 타입 매개변수를 추가한다.
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
//myIdentity에 identity 함수 자체를 할당한다.
let myIdentity: GenericIdentityFn<number> = identity;
console.log(myIdentity(1));
interface ResponseData<T> {
status: number;
payload: T;
message: string;
}
const successResponse: ResponseData<string> = {
status: 200,
payload: 'ok',
message: 'Success',
};
const userResponse: ResponseData<{ name: string; age: number }> = {
status: 200,
payload: { name: 'John', age: 30 },
message: 'User fetched',
};
타입 별칭
타입 별칭(Type Alias) 이름 뒤에 제네릭 타입 매개변수를 추가한다.
type GenericIdentityFn<T> = { value: T };
const stringContainer: GenericIdentityFn<string> = { value: "Hello, World!" };
const numberContainer: GenericIdentityFn<number> = { value: 42 };
type LinkedList<T> = T & { next: LinkedList<T> | null };
let node3: LinkedList<number> = { value: 3, next: null };
let node2: LinkedList<number> = { value: 2, next: node3 };
let node1: LinkedList<number> = { value: 1, next: node2 };
// 문자열을 위한 LinkedList
let strNode1: LinkedList<{ value: string }> = { value: 'First', next: null };
let strNode2: LinkedList<{ value: string }> = { value: 'Second', next: strNode1 };
클래스
클래스 이름 뒤에 제네릭 타입 매개변수를 추가한다.
클래스의 메서드나 프로퍼티 타입으로 사용할 수 있다.
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
class DataStore<T> {
private data: T[] = [];
add(item: T) {
this.data.push(item);
}
remove(item: T) {
this.data = this.data.filter((d) => d !== item);
}
getAll(): T[] {
return this.data;
}
}
const numberStore = new DataStore<number>();
numberStore.add(1);
numberStore.add(5);
const numbers = numberStore.getAll(); // [1, 5]
const stringStore = new DataStore<string>();
stringStore.add("hello");
stringStore.add("world");
const strings = stringStore.getAll(); // ['hello', 'world']
주의점
- Static과 같은 정적변수는 제네릭으로 관리할 수 없다.
다중 제네릭
제네릭은 한개 뿐 아니라 다중으로 사용할 수 있다,
보통 T, K, V 정도를 사용하는 것 같다.
함수
두 개의 서로 다른 타입 T와 K를 입력으로 받고, 튜플로 반환하는 함수를 정의
function pair<T, K>(x: T, y: K): [T, K] {
return [x, y];
}
// 사용 예
const result = pair<string, number>('hello', 42);
// result의 타입은 [string, number]
인터페이스
interface pair<K, V> {
key: K;
value: V;
}
const result : pair<string, number> = { key: 'age', value: 30 };
클래스
class Map<K, V> {
private items: Array<{ key: K; value: V }> = [];
setItem(key: K, value: V): void {
this.items.push({ key, value });
}
getItem(key: K): V | undefined {
const foundItem = this.items.find(item => item.key === key);
return foundItem ? foundItem.value : undefined;
}
}
// 사용 예
const map = new Map<string, number>();
map.setItem('apples', 5);
map.setItem('bananas', 10);
const apples = map.getItem('apples'); // 5
타입을 조합한 타입
복잡한 데이터 구조에서 여러 타입 조합 가능하다.
Dictionary 타입을 정의할 때 키의 타입(K)과 값을 배열로 갖는 타입(V[])을 사용할 수 있다.
type Dictionary<K, V> = {
[key in K]: V[];
};
// 사용 예
const dict: Dictionary<string, number> = {
ages: [35, 42, 63],
scores: [75, 82, 94]
};
extends
타입스크립트에서 extends 키워드는 주로 두 가지 경우에서 사용된다.
- 클래스 상속
- 제네릭 타입 제약
클래스 상속에서의 extends
클래스 상속을 할 때 extends 키워드로 기존 클래스의 모든 속성과 메서드를 상속받은 새로운 클래스를 생성할 수 있다.
(이 부분은 제네릭과 관련 없지만 extends 의 역할을 위해 추가함)
class Animal {
move() {
console.log("Moving along!");
}
}
class Dog extends Animal {
bark() {
console.log("Woof! Woof!");
}
}
const dog = new Dog();
dog.move(); // "Moving along!"
dog.bark(); // "Woof! Woof!"
제네릭 타입 제약에서의 extends
제네릭 타입 제약에서 extends를 사용하여 특정 타입이나 인터페이스를 만족하는 타입 매개변수만을 받도록 제한할 수 있다.
이는 타입 매개변수가 특정 속성이나 메서드를 갖고 있음을 보장한다.
여기서도 두가지 경우로 나뉜다.
1. 키가 객체에 있는지 확인하기
여기서 K extends keyof T는 K가 T의 키 중 하나임을 의미한다.
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
객체 T와 해당 객체에서의 키 K를 받는 함수에서, K가 T의 키인지 확인하는 제약을 추가할 수 있다
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
const x = { a: 1, b: 2, c: 3, d: 4 };
// 동작합니다: 'a'는 x의 키
const aValue = getProperty(x, "a");
// 오류: 'm'은 x의 어떤 키와도 일치하지 않음
// const mValue = getProperty(x, "m");
2. 두 객체 타입이 호환되는지 확인하기
두 제네릭 타입을 받아서, 하나가 다른 하나의 서브 타입인지 확인하는 함수
제네릭 제약 T extends U는 T가 U의 모든 속성을 포함해야 함
person 객체는 employee 객체의 모든 속성을 포함하고 있으므로, 함수 호출은 성공한다.
function extend<T extends U, U>(first: T, second: U): T {
return first;
}
const person = { name: 'John', age: 30 };
const employee = { name: 'Jane' };
// 이 경우, 'person' 객체는 'employee' 객체의 모든 속성('name')을 가지고 있습니다.
const extendedPerson = extend(person, employee);
console.log(extendedPerson);
제네릭의 기본 값(Default Value)
제네릭 타입을 사용할 때, 제네릭 타입을 명시적으로 지정하지 않았을 경우 적용될 기본 타입을 정의하는 기능이다.
아래 코드에서 TempResponse 타입은 Data가 기본으로 {status: number}를 가진다.
type ApiResponse<Data = {status: number}> = {
data: Data;
isError: boolean;
};
//제네릭을 직접적으로 넣어주지 않으면
// TempResponse는 ApiResponse<{status: number}>와 동일하게 처리됩니다.
// 따라서 TempResponse의 data 속성은 { status: number } 타입을 가집니다.
type TempResponse = ApiResponse;
이렇게 직접적으로 제네릭을 지정해주지 않으면 기본으로 넣어준 { status: number } 이게 타입으로 적용된다.
type ApiResponse<Data = { status: number }> = {
data: Data;
isError: boolean;
};
type TempResposne = ApiResponse<{ status: boolean }>;
'타입스크립트' 카테고리의 다른 글
타입스크립트 interface 인덱서블 타입 (0) | 2024.03.12 |
---|---|
타입스크립트란? (0) | 2024.03.10 |
ts-node로 타입스크립트 파일 바로 실행 (0) | 2024.03.04 |
타입스크립트 declare 사용법 (0) | 2024.03.01 |
타입스크립트 타입 (0) | 2023.08.30 |