Day38 - TIL 타입스크립트

정태호·2023년 7월 24일
0

TIL

목록 보기
16/23
post-thumbnail

제네릭(Generic)


function toObj(a: string, b: string): { a: string; b: string }
function toObj(a: number, b: number): { a: number; b: number }
function toObj(a: boolean, b: boolean): { a: boolean; b: boolean }
function toObj(a: string | number | boolean, b: string | number | boolean): { a: string | number | boolean; b: string | number | boolean} {
  return { a, b };
}

toObj('A', 'B');
toObj(1, 2);
toObj(true, false);

위 코드처럼 들어온 매개변수들을 객체로 만들어 반환하는 매우 간단한 코드에서도 들어오는 매개변수가 모두 string, number, boolean 으로 지정해 줘야 한다면 배웠던 오버로딩 을 사용하여 구현하면 선언부의 코드가 매우 길어지는 걸 볼 수 있다...

제네릭 사용

  • <T>라는 부분을 타입변수라고 한다. 타입을 내부에서 재사용 가능
  • 함수를 호출하는 단계에서 특정한 타입을 넣어줄 수 있다.
  • <>가 있는 부분과 없는 부분이 존재하는데 두 코드 모두 문제없이 동작한다.
  • extends string | number | boolean 이 부분의 코드처럼 제약조건을 넣어줌으로써 어떤 타입만 가질 수 있는지 정의할 수 있다.
function toObj<T extends string | number | boolean>(a: T, b: T): { a: T; b: T } {
	return { a, b };
}

toObj('A', 'B');
toObj<number>(1, 2);
toObj<boolean>(true, false);
toObj(null,null) // Error

toObj('A', 'B') 이 부분에서 <>가없어도 되는 이유는 타입을 호출하는 부분에서 첫번째 인자가 string, number 등의 값을 가지고 있으므로 <T> 에는 첫번째 인자의 type이 정의되고 그 뒤에 코드는 자연스럽게 그 값으로 정의되게 된다!!! 뒤에 코드들도 명시적으로 작성해 줄 필요는 없다.

  • 인터페이스나 type alias, 클래스 등에서도 사용 가능하다!
interface ToObj<T> {
  a: T;
  b: T;
}

function toObj<T extends string | number | boolean> (a: T, b: T) : ToObj<T>{
  return {a,b}
}

type alias 제네릭


type User<T, U, V> = {
  name : T
  age : U
  isValid : V
} | [T,U,V]; // 튜플타입

const jth: User<string, number, boolean> = {
  name: 'JTH',
  age: 25,
  isValid: true,
};
const neo: User<string, number, boolean> = {
  name: 'NEO',
  age: 10,
  isValid: true,
};
const prodo: User<string, number, boolean> = {
  name: 'PRODO',
  age: 40,
  isValid: false,
};

const a: User<string, number, boolean> = ['A', 40, false];
const b: User<string, number, boolean> = ['B', 40, false];
const c: User<string, number, boolean> = ['C', 40, false];
  • 재사용이 편한 코드로 수정
type User<T, U, V> = {
  name : T
  age : U
  isValid : V
} | [T,U,V]; // 튜플타입

type U = User<string,number,boolean>


const jth: U = {
  name: 'JTH',
  age: 25,
  isValid: true,
};
const neo: U = {
  name: 'NEO',
  age: 10,
  isValid: true,
};
const prodo: U = {
  name: 'PRODO',
  age: 40,
  isValid: false,
};

const a: U = ['A', 40, false];
const b: U = ['B', 40, false];
const c: U = ['C', 40, false];

class 제네릭

type T = 'Apple' | 'Banana' | 'Mango' // 제약조건이 걸리면서 이렇게 추론함
class Basket<T extends string> {
  public items: T[]
  constructor(...rest: T[]){
    this.items = rest
  }
  putItem(item: T) {
    this.items.unshift(item)
  }
  takeOutItems(count: number){
    return this.items.splice(0, count)
  }
}

const fruitsBasket = new Basket<string>('Apple',"Banana","Mango")
fruitsBasket.putItem('Cherry') // 명시적으로 타입을 지정해주면서 에러 해결
const fruits = fruitsBasket.takeOutItems(2)
console.log(fruits) //['Cherry', 'Apple']
console.log(fruitsBasket.items) //['Banana','Mango']

📌 주의할 점 : 제약조건을 걸게되면 타입스크립트는 최대한 구체적이고 특정한 타입으로 추론하려고 하기 때문에 Apple,Banana,Mango 타입이 되어버린다. 이럴 때는 명시적으로 타입을 지정해 줄 필요가 있다!!

조건부 타입(Conditional Types)

  • 삼항연산자를 통해 참 거짓 판별로 타입 반환
type MyType<T> = T extends string | number ? boolean : never;

const a: MyType<string> = true;
const b: MyType<number> = true;
const c: MyType<null> = true; //never 타입이여서 Error 발생

utility type(유틸리티 타입)

  • 제약조건으로 타입을 제거 후 필요한 타입만 사용할 수 있다..?
type MyExclude<T, U> = T extends U ? never : T; //유틸리티 타입
type MyUnion = string | number | boolean | null;

const a: MyExclude<MyUnion, boolean | null> = 123; // 문자이거나 숫자여야한다 string | number
const a: Exclude<MyUnion, boolean | null> = 123;

위의 예제에서는 (Utility Type) 내장이 된 타입(Exclude)이 존재하여 직접 만들어 줄 필요없이 Exclude로 사용해도 동작한다.

다른 예제

  • keyof : 키 밸류 형태에서 키 부분만 추출해서 유니온 타입으로 만들어준다.
type IsPropertyType<T, U extends keyof T, V> = T[U] extends V ? true : false; //T[U] -> User['name'] false 타입

type Keys = keyof User; // 'name' | 'age'
interface User {
  name: string;
  age: number;
}

const result: IsPropertyType<User, 'name', number> = true; //Error

name 속성은 number 타입이 아니므로 결국 false 값이 반환되고 위 예제에서는 true라고 선언했으므로 Error가 나타나게 된다!!

infer 키워드로 타입 추론

  • 조건부 타입 - infer(추론하다 라는 의미)
  • extends 키워드를 기준으로 왼쪽의 구조와 지금 현재 infer쪽의 구조를 잘 살펴보자.
// number[] extends (infer I)[] -> number 타입이 추론됨
type ArrayItemType<T> = T extends (infer I)[] ? I : never;

const numbers = [1, 2, 3];
const a: ArrayItemType<typeof numbers> = 123; //<typeof numbers> -> number[]
const b: ArrayItemType<boolean> = 123;
                       
const fruits = ['Apple', 'Mango', 'Orange'];
const hello = () => {};
const c: ArrayItemType<typeof fruits> = 'abc'; //string[]
const d: ArrayItemType<typeof hello> = 'abc'; //Error never 타입
// typeof hello -> () => void

즉 infer 키워드를 통해 타입 변수를 정의할껀데 어떤 타입인지 알 수 있는지 없는지 알아보기 -> 타입추론이 가능한지 아닌지

다른 예제

type SecondArgumentType<T> = T extends (f: any, s: infer S) => any ? S : never;

function hello(a: string, b: number) {}
const result: SecondArgumentType<typeof hello> = 123; // number 타입
//(a: string,b: number) => void

2

// 제약조건 : 함수 데이터여야 한다
type MyReturnType<T extends (...args: any) => any> 
	= T extends (...args: any) => infer R ? R : any;

function add(x: string, y: string) {
  return x + y;
}

const result: ReturnType<typeof add> = 'Hello'; //string 타입이 됨
// (x: string, y: string) => string
profile
주니어 프론트엔드 개발자가 되고 싶습니다!

0개의 댓글