본문 바로가기
내일배움캠프/Today I Learned

[TIL 2023.07.31] 타입스크립트 generic

by 괴코딩 2023. 7. 31.

💻오늘 배운 내용

Generic

타입을 함수의 파라미터처럼 사용한다. (선언 시에는 타입 파라미터만 적어주고, 생성하는 시점에 사용하는 타입을 결정)

함수의 인자로 어떤 타입이 들어갔고 어떤 값이 반환되는지는 알 수 없을 때 유용하다.

 

예시코드

function getSize(arr:number):number{
	return arr.length;
};

const arr = [1, 2, 3];
getSize(arr);  //3

getSize라는 함수가 있고, 호출 시 인자로 숫자배열을 넣어준다고 치자.

파라미터의 타입은 넘버, 함수의 타입도 배열의 길이를 반환하기 때문에 넘버가 된다

 

하지만 여러 형태의 배열에서 쓰인다면 어떨까?

function getSize(arr : number[]): number {
	return arr.length;
};

const arr1 = [1, 2, 3];
getSize(arr1);  //3

const arr2 = ["a", "b", "c"];
getSize(arr2);  //3

const arr3 = [true, false, false];
getSize(arr3);  //3

const arr4 = [{}, {}, {name : "KIM"}];
getSize(arr4);  //3

arr1을 제외하고선 죄다 에러가 뜰 것이다. 타입이 숫자배열이 아니니까.

그런 경우 함수 오버로딩이나 '|'를 이용하여 union타입으로 지정해 줄 수 있다.

function getSize(arr : number[] | string[] | boolean[] | object[]): number {
	...생략
}

하지만 그렇게 되면 새로운 배열이 생길때 마다 계속 계속 추가해줘야 하는 단점이 생긴다.

이럴 때 사용하기 좋은 것이 generic이다.

 

function getSize<T>(arr : T[]): number {
	return arr.length;
};

함수이름 옆에 <>를 이용하여 들어갈 인자를 적어준다. (보통 선언 시에 T를 많이들 사용한다.)

여기서 T는 어떤 타입을 전달 받아서 이 함수를 사용할 수 있도록 한다.

즉 getSize 함수의 매개변수 타입은 T의 배열이 된다.

 

그리고 사용 시점에 이 T가 어떤 타입인지 결정해주면 된다. 

function getSize<T>(arr : T[]): number {
	return arr.length;
};

const arr1 = [1, 2, 3];
getSize<number>(arr1);  //3

const arr2 = ["a", "b", "c"];
getSize<string>(arr2);  //3

const arr3 = [true, false, false];
getSize(arr3);  //3

const arr4 = [{}, {}, {name : "KIM"}];
getSize(arr4);  //3

실행 시 함수이름 옆에 타입을 특정해주면 되는 것.

하지만 사실 적어주지 않아도 타입스크립트는 전달되는 파라미터를 보고 어떤 타입인지 알 수 있다.

선언할 때 <T>라고 적어뒀으니까!

 

따라서 특정타입을 강제하고 싶은 경우에만 작성해도 상관없다.

 

인터페이스에서 generic 사용 할 때

interface Mobile {
    name: string;
    price: number;
    option: any;
}

// 옵션에 어떤 타입이 들어오는지 모르는 상태일 때, 
// 가능한 옵션의 타입을 모두 적어주는 것은 비효율적이니까 제네릭을 이용하면 좋다.

interface Mobile<T> {
    name: string;
    price: number;
    option: T;
}

위 함수의 예시를 보자.

const m1: Mobile = {
    name: "s21",
    price: 1000,
    option: {
        color: "red",
        coupon: false,
    },
};

const m2: Mobile = {
    name: "s20",
    price: 900,
    option: "good",
};

이 상태로는 m1, m2 둘 다 에러가 난다. 여기서 타입을 전달해 주어야 한다.

const m1: Mobile<object> = {
    name: "s21",
    price: 1000,
    option: {
        color: "red",
        coupon: false,
    },
};
// 혹은 옵션 객체의 모습이 정해져 있다면 Mobile<{color : string; coupon : boolean }>
// 이라고 적어줘도 무방하다.


const m2: Mobile<string> = {
    name: "s20",
    price: 900,
    option: "good",
};

 

객체의 속성을 제약하는 방법

function getProperty<T, O extends keyof T>(obj: T, key: O) {
  return obj[key];  
}
let obj = { a: 1, b: 2, c: 3 };

getProperty(obj, "a"); // okay
getProperty(obj, "z"); // error: "z"는 "a", "b", "c" 속성에 해당하지 않습니다.

generic을 선언할 때 <O extends keyof T> 부분에서 첫 번째 인자로 받는 객체에 없는 속성들은 접근할 수 없게끔 제한할 수 있다.

 

🧐궁금점과 부족한 내용

실행 시 함수이름 옆에 타입을 특정해주지 않아도 타입스크립트는 전달되는 파라미터를 보고 어떤 타입인지 알 수 있다.

선언할 때 <T>라고 적어뒀으니까!

 

라고 앞서 말했었는데

 

특정타입을 강제하고 싶은 경우에만 작성해도 상관없다면 굳이 타입스크립트를 쓰는 이유가 있는가..

자바스크립트와 다른게 무언가~ 의문이 드는 것은 사실이다.

 

이 부분은 내일 튜터님 찾아가서 따로 한번 물어봐야겠다.

 

📋레퍼런스

Type Script Deep Dive

[TypeScript] 타입스크립트 제네릭(Generic), Factory Pattern with Generics

타입스크립트 핸드북

반응형