TypeScript

TypeScript 제네릭 정의

wlals5855 2024. 9. 19. 22:57

⭐제네릭

✔️제네릭 정의

  • 재사용 가능한 컴포넌트(코드단위)를 만드는데 사용
  • 해당 컴포넌트가 처리할 데이터타입을 미리 지정하니 않고
    해당 컴포넌트를 사용하는 시점에서 원하는 데이터 타입 지정

✔️제네릭 필요성

  • 코드의 중복을 줄임
  • 재사용 가능한 컴포넌트를 생성
  • 타입 안정성 보장
  • 컴파일 시점에서 타입을 체크 ('런타임' 환경에서 발생할 수 있는 에러를 방지) 

컴파일 타임

  • 소스코드를 과장

✔️ 제네릭 기본 문법

  • 사용할 컴포넌트(변수, 함수, 클래스 등)의 이름 뒤에 꺽쇠괄호(<>) 안에 '타입 변수'를 지정
  • 함수나 클래스 등을 사용할 때 활용할 타입 명시


"타입 변수"

  • 임의의 키워들르 사용하여 명시
  • 일반적으로 대문자 알파벳 하나를 사용(T: type, U,...)
  • 해당 타입 변수는 컴포넌트 내에서 실재 데이터 타입의 자리를 대신
function generic<T>(arg: T): T { // 함수 정의 시 타입 변수 지정
  // 타입 변수 T (단일 타입 변수)
  return arg;
}

let str1 = generic<string>('안녕하세요'); // 함수 호출 시 타입 변수에 활용할 타입을 명시
let str2 = generic('hello'); // 오류X - 제네릭의 경우 타입 변수에 값을 유추 가능
// let str3 = generic<string>(100); - Error

generic<number>(123);
generic<boolean>(true); 

//? +) 여러 제네릭 타입 변수 지정
// : 여러 개의 독립적인 타입을 처리할 경우 사용

function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

let pairOutput = pair<string, number>('안녕', 345);
let pairOutput2 = pair<number, string>(345, '안녕');

function pair2<T, U>(first: T, second: T): U { // U를 T[]로 작성 가능
  return [first, second] as U;
}

let pair2Output = pair2<string, string[]>('hello', 'hi');
// ['hello', 'hi']

 

 ✔️ 제네릭을 사용하는 컴포넌트들 예

변수

let a;

a = 10;
a = '안녕';

 

제네릭 함수

  • 주로 매개변수의 타입을 지정하는 경우가 많음
function genericFunc<T>(args: T[]): T[] { // string[], number[]
  console.log(`배열의 길이: ${args.length}`);
  return args;
}

let resultFunc = genericFunc<boolean>([true, false, false]); // 배열의 길이: 3
console.log(`함수의 반환값: ${resultFunc}`); // 함수의 반환값: true,false,false

 

제네릭 인터페이스

  • 타입 매개변수 T를 가지는 인터페이스
interface IGeneric<T> {
  (key: T): T; // 인터페이스 내부의 속성 타입을 명시
}

function example<T>(arg: T): T {
  return arg;
}

let myGeneric: IGeneric<number> = example;

console.log(example(5));
console.log(myGeneric(5));

 

제네릭 클래스

class GenericClass<T> {
  value: T;
  add: (x: T, y: T) => T; // 해당 클래스로 인스턴스화될 때 인자로 함수를 전달받음

  constructor(value: T, addFunc: (x: T, y: T) => T) {
    this.value = value;
    this.add = addFunc;
  }
}

class BasicClass {
  value: string;
  
  constructor(value: string, addFunc: (x: string, y: string) => string) {
    this.value = value;
    this.add = addFunc;
  }

  add: (x: string, y: string) => string;
}

let myGenericNumber = new GenericClass<number>(0, function (x, y) { return x + y });
console.log(myGenericNumber.add(4, 5)); // 9

let myGenericString = new GenericClass<string>('', function (x, y) { return `${x}${y}` });
console.log(myGenericString.add('이', '승아')); // 이승아

 

제네릭 함수 구현

function reverseArray<T>(array: T[]): T[] {
  // 배열 메서드: reverse()
  // >> [1, 2, 3].reverse() === [3, 2, 1]

  let reverseArr = array.reverse();
  return reverseArr;
}

let stringArr = reverseArray(['1', '2', '3']);
console.log(stringArr); // [ '3', '2', '1' ]

let booleanArr = reverseArray([true, false, false, false]);
console.log(booleanArr); // [ false, false, false, true ]

 

제네릭 인터페이스 정의

interface KeyValue<K, V> { // K: key의 타입, V: value의 타입
  key: K,
  value: V
}

let keyValue: KeyValue<string, number> = {
  key: '이승아',
  value: 123
}

let anotherKeyValue: KeyValue<boolean, number> = {
  key: true,
  value: 200
}

 

 제네릭의 제약 조건

✔️제약 조건(constraints) 

타입 매개변수가 특정 조건을만족해야한다는 것을 명시하는 방법
제네릭 타입의 사용 범위를 제한

interface ILength {
  length: number;
}

 

 

✔️T라는 타입 변수는 ILength 인터페이스를 포함하는 타입이어야 한다.

  • '타입 변수' extends(확장) '반드시 포함할 타입'
  • T 타입의 데이터는 반드시 length 속성을 가져야 함
function constraints<T extends ILength>(arg: T): T {
  console.log(arg.length);
  return arg;
}

 

 

let data01 = constraints({
  length: 10,
  value: 3,
  addedOption: '안녕하세요'
});
  • length 속성을 반드시 포함하는 객체를 인자로 전달
  • 추가로 다른 속성 명시 가능 (확장)
console.log(data01); // 10, { length: 10, value: 3, addedOption: '안녕하세요' }

 

최소한의 속성을 가진 객체 찾기

keyof 연산자

  • 객체 형태의 타입을 따로 속성만 뽑아 유니온 타입으로 만들어주는 연산자
  • 모든 키의 이름을 유니온 타입으로 반환
type Type = { // name과 age 속성을 가지는 Type 타입 별칭
  name: string;
  age: number;
}

type Union = keyof Type;
// Union = name | age;

 

✔️조건부 타입

  • 타입 매개변수에 대한 조건 표현식을 사용하여 타입을 결정하는 방식
  • if문과 유사한 기능
type Check<T> = T extends string ? 'String' : 'Not a String';

type Type1 = Check<string>; // String
type Type2 = Check<number>; // Not a String

function checkType<T>(value: T): Check<T> {
  let result = typeof value === 'string' ? ('String' as Check<T>) : ('Not a String' as Check<T>);
  return result;
}

console.log(checkType('안녕하세요')); // String
console.log(checkType(500)); // Not a String