[TypeScript] 제네릭 본문

Web/TypeScript

[TypeScript] 제네릭

미니모아 2022. 8. 25. 09:50
반응형

제네릭

내장 제네릭 & 제네릭이란?

제네릭 타입이란 타입스크립트에 내장된 타입이며 다른 타입과 연결된다.

제네릭 타입을 사용하면 보다 나은 타입 안정성을 확보할 수 있다.

const names: Array<string> = []; //string[]
names[0].split(' ');

const promise = new Promise<number>((resolve, reject) => {
  setTimeout(() => {
    resolve(10);
  }, 2000);
});

promise.then(data => {
  data.split(' '); // error :  Property 'split' does not exist on type 'number'.
})

Generic Functions

제네릭 함수를 직접 만들어보자.

두 개의 객체를 인자로 받아 합치는 merge 함수이다.

function merge(objA: object, objB: object) {
  return Object.assign(objA, objB);
}

const mergeObj = merge({name: 'Max'}, {age: 30});
mergeObj.age;// error

merge는 되지만 mereg된 객체의 속성에 접근할 수 없다. 타입스크립트에게는 object가 구체적인 타입이 아니기 때문에 미상의 두 객체의 인터섹션이 반환된다는 사실외에는 추론할 수가 없다.

이때 제네릭 타입을 사용하면 두 매개변수가 서로 다른 타입이 될 수 있다고 타입스크립트에게 알려줄 수 있으므로 무작위의 객체 타입으로 작업하는 것이 아닌 다양한 타입 데이터를 얻고자 한다는 것을 타입스크립트가 인식하게 되고 해당 객체들의 인터섹션을 반환하므로 mergeObj에 저장되는 데이터가 두 입력값 데이터의 인터섹션임을 타입스크립트가 인식할 수 있게 된다.

대체로 첫 번째 인자는 T, 두 번째 인자는 U로 쓴다.

function merge<T, U>(objA: T, objB: U) {
  return Object.assign(objA, objB);
}

console.log(merge({name: 'Max'}, {age: 30}));

const mergeObj = merge({name: 'Max'}, {age: 30});
mergeObj.age; 

Constraints

extends 키워드를 통해 제네릭 타입에 특정한 제약 조건을 설정할 수도 있다.

function merge<T extends object, U extends object>(objA: T, objB: U) {
  return Object.assign(objA, objB);
} 

속성 제한

특정 타입을 지정해서 제한하는 것 외에 특정 속성을 가진 타입만 인수로 들어오도록 제한할 수 있다. length를 가진 타입만 인자로 들어올 수 있도록 제한해보자

interface Lengthy {
  length : number;
}
function countAndDescription<T extends Lengthy>(element: T): [T, string] {
  let descriptionText = 'Got no value.';
  if (element.length === 1) {
    descriptionText = 'Got 1 element.';
  } else if (element.length > 1) {
    descriptionText = `Got ${element.length} elements`;
  }
  return [element, descriptionText];
}

console.log(countAndDescription('Hi there!'));

string, array는 받을 수 있지만 숫자를 입력하면 에러가 나는 것을 볼 수 있다.

console.log(countAndDescription('Hi there!'));
console.log(countAndDescription(['Hi', 'Hello']);
console.log(countAndDescription(10)); // error

keyof 제약조건

제네릭 타입을 다른 제네릭 타입의 키로 지정할 수도 있다. keyof 키워드를 사용한다.

function extractAndConvert<T extends object, U extends keyof T>(obj: T, key: U) {
  return `Value ${obj[key]}`
}

console.log(extractAndConvert({name: 'Max'}, 'name'));

제네릭 클래스

클래스에도 제네릭을 사용할 수 있다.

class DataStorage<T extends string | number | boolean > {
  private data: T[] = [];
  addItem(item: T) {
    this.data.push(item);
  }

  removeItem(item: T) {
    this.data.splice(this.data.indexOf(item), 1);
  }

  getItems() {
    return [...this.data];
  }
}

const textStorage =  new DataStorage<string>();
textStorage.addItem('Max');
textStorage.addItem('Manu');
console.log(textStorage.getItems());


const numberStorage = new DataStorage<number> ();

// const objStorage = new DataStorage<object>();
// const maxObj = {name: 'Max'};
// objStorage.addItem(maxObj);
// objStorage.addItem({name: 'Manu'});

// objStorage.removeItem(maxObj);
// console.log(objStorage.getItems());

제네릭 유틸리티 타입

파셜(Partial) 타입

객체 타입이나 인터페이스 중 하나를 일시적으로 선택적으로 바꿔서 포함된 모든 속성을 일시적으로 선택적으로 해야하는 경우에 사용할 수 있다. 타입스크립트에게 아직 해당 객체가 완전히 생성되지 않은 부분임으로 알리는 것이다.

interface CourseGoal {
  title: string;
  description: string;
  completeUntil: Date;
}

function createCourseGoal(
  title: string,
  description: string,
  date: Date
): CourseGoal {
  let courseGoal: Partial<CourseGoal> = {};

  courseGoal.title = title;
  courseGoal.description = description;
  courseGoal.completeUntil = date;

  return courseGoal as CourseGoal;
}

Readonly

속성을 변경하거나 이 객체에 새 속성을 추가할 수 없도록 할 수 있다.

const names: Readonly<string[]> = ['Max', 'Sports'];
names.push('Manu'); // error
반응형
Comments