다음은 원기둥의 반지름과 높이, 표면적, 부피를 출력하는 코드이다.
console.log('Cylinder 1 X 1', //r: 1 h: 1
'Surface area:', 6.283185 * 1 * 1 + 6.283185 * 1 * 1, //2πrh + 2πr^2
'Volume: ', 3.14159 * 1 * 1 * 1); //πr^2h
console.log('Cylinder 1 X 2', //r: 1 h: 2
'Surface area:', 6.283185 * 1 * 1 + 6.283185 * 2 * 1, //2πr^2 + 2πrh
'Volume: ', 3.14159 * 1 * 2 * 1); //πr^2h
console.log('Cylinder 2 X 1', //r: 2 h: 1
'Surface area:', 6.283185 * 2 * 1 + 6.283185 * 2 * 1, //2πrh + 2πr^2
'Volume: ', 3.14159 * 2 * 2 * 1); //πr^2h
비슷한 코드가 반복되어있어 보기 불편하다.
함수, 상수, 루프의 반복을 제거해 코드를 개선해 보자 ! (DRY 원칙)
const surfaceArea = (r,h) => 2* Math.PI * r * (r * h);
const volume = (r,h) => Math.PI * r * r * h;
for(const [r,h] of [[1,1],[1,2],[2,1]]){
console.log(
`Cylinder ${r} x ${h}`,
`Surface area: ${surfaceArea(r,h)}`,
`Volume: ${volume(r,h)}`);
}
코드의 반복을 줄이는 가장 간단한 방법은 타입에 이름을 붙이는 것이다.
function distance(a: {x: number, y: number}, b: {x: number, y: number}){
return Math.sqrt(Math.pow(a.x -b.x, 2) + Math.pow(a.y - b.y,2));
}
interface Point2D{
x: number;
y: number;
}
function distance(a: Point2D, b: Point2D){}
상수를 사용해서 반복을 줄이는 기법을 타입 시스템에 적용했다.
그러나 중복된 타입은 문법에 의해 가려지기도 한다.
예를 들어 아래와 같이 함수가 같은 타입 시그니처를 공유하고 있을 때
시그니처를 명명된 타입으로 분리해 낼 수 있다.
function get(url : string, opts: Options) : Promise<Response>{}
function post(url : string, opts: Options) : Promise<Response>{}
type HTTPFunction = (url: string, opts: Options) => Promise<Response>; //시그니처를 타입으로 분리
const get: HTTPFunction =(url, opts) => {};
const post: HTTPFunction =(url, opts) => {};
또는 인터페이스를 확장하여 반복을 제거할 수 있다.
interface Person {
firstName: string;
lastName: string;
}
interface PersonWithBirthDate extends Person{
birth: Date;
}
전체 상태를 표현하는 State 타입과 부분만 표현하는 TopNavState 가 있다고 가정해보자.
interface State{
userId: string;
pageTitle: string;
recentFiles: string[];
pageContents: string;
}
interface TopNavState{
userId: string;
pageTitle: string;
recentFiles: string[];
}
TopNavState 를 확장해서 State 를 구성하는 것 보다
State 의 부분집합으로 TopNavState 를 정의하는 것이 바람직하다
type TopNavState = {
userId: State['userId'];
pageTitle: State['pageTitle'];
recentFiles: State['recentFiles'];
};
만약 이때 State 내의 pageTitle 타입이 바뀌면 TopNavState 에도 반영된다.
'매핑된 타입' 을 사용해 코드를 더 줄일 수 있다.
type TopNavState = {
[k in 'userId' | 'pageTitle' | 'recnetFiles']: State[k]
};
이 때 TopNavState 에 마우스를 올리면 앞의 예제와 완전히 동일하게 정의되어있는 것을 확인할 수 있다.
매핑된 타입
매핑된 타입은 기존 타입을 변환해서 새로운 타입을 만들어내는 기능이고, 주로 객체나 배열의 타입변환에 사용된다.
매핑된 타입은 배열의 필드를 루프 도는 것과 같은 방식이다.
이 패턴은 표준 라이브러리에서도 일반적으로 찾을 수 있으며, Pick 이라고 한다.
type Pick<T, K> = { [k in K]: T[k] };
매핑된 타입은 `in` 키워드를 사용해
배열이나 튜플 등의 타입에 대해 루프를 도는 것과 같은 방식으로 새로운 타입을 만들어낸다.
type TopNavState = Pick<State, 'userId' | 'pageTitle' | 'recentFiles'>;
- Pick 을 사용해서 State 에서 userId, pageTtiel, recentFiles 필드만 추출해서 새로운 타입을 만들 수 있다.
- Pick 은 제네릭 타입으로 첫번째 인자로 객체타입을, 두번째 인자로 해당 객체 타입에서 추출하고자 하는 필드의 이름들을 문자열 리터럴 타입 배열로 받는다.
- TopNavState 타입은 userId, pageTtiel, recentFiles 필드만을 가진 새로운 타입이 되었다.
매핑된 타입과 `Pick` 을 사용하면 코드를 간결하게 만들 수 있다.
태그된 유니온에서도 다른 형태의 중복이 발생할 수 있다.
- 태그된 유니온 : 유니온 타입에 문자열 리터럴 타입을 추가해서 타입 안전성을 높인 방법
interface SaveAction{
type: 'save';
}
interface LoadAction{
type: 'load';
}
type Action = SaveAction | LoadAction;
Action 유니온을 인덱싱하면 타입 반복없이 ActionType 을 정의할 수 있다.
type ActionType = 'save' | 'load'; //타입의 반복 !
type ActionType = Action['type']; //타입은 "save" | "load"
Action 유니온에 타입을 더 추가하면 ActionType 은 자동적으로 그 타입을 포함한다.
ActionType 은 Pick 을 사용해서 얻게되는 type 속성을 갖는 인터페이스와는 다르다. (객체)
type ActionRec = Pick<Action, 'type'>; //{type: "save" | "load"}
▷ keyof
interface Options{
width: number;
height: number;
ocolor: string;
label: string;
}
interface OptionsUpdate{
width?: number;
height?: number;
color?: string;
label?: string;
}
매핑된 타입과 keyof 를 사용하면 동일하게 OptionsUpdate 를 만들 수 있다.
keyof 는 타입을 받아서 속성 타입의 유니온을 반환한다.
type OptionsUpdate = {[k in keyof Options]?: Options[k]};
편집기를 통해 코드와 동일하게 정의되어있는 것을 확인할 수 있다.
▷ typeof
값의 형태에 해당하는 타입을 정의하고 싶을 때 typeof
const INIT_OPTIONS = {
width: 640,
height: 480,
color: '#00FF00',
label: 'VGA',
};
interface Options{
width: number,
height: number,
color: string,
label: string,
}
▷ ReturnType
함수나 메서드의 반환값에 명명된 타입을 만들 때 ReturnType
- ReturnType 은 함수의 반환타입을 추출하는 유틸리티 타입이다.
- ReturnType 을 사용해 함수의 반환 타입을 변수로 선언하거나 다른 타입의 매개변수로 전달할 수 있다.
예제 1 )
function calculatePrice(quantity: number, price: number): number {
return quantity * price;
}
calculatePrice 는 quantity와 price를 받아서 곱한 값을 반환한다.
함수의 반환 타입은 number 이다.
ReturnType 을 사용해서 calculatePrice 함수의 반환 타입을 추출해서 변수로 선언할 수 있다.
type CalculatePriceReturnType = ReturnType<typeof calculatePrice>;
ReturnType<typeof calculatePrice> 을 통해 함수의 반환 타입인 number 를 CalculatePriceReturnType 타입에 할당한다.
const totalPrice : CalculatePriceReturnType = 100;
totalPrice 변수에 CalculatePriceReturnType 의 값인 100을 할당했다.
예제 2 )
function getUserInfo(userId: string){
//...
return {
userId,
name,
age,
height,
weight,
favoriteColor
};
}
type UserInfo = ReturnType<typeof getUserInfo>;
ReturnType 은 함수의 타입인 typeof getUserInfo 에 적용되었다.
적용대상이 값인지 타입인지 정확하게 알고 구분해서 처리해야 한다.
▷ 제너릭타입
- 제너릭 타입은 타입을 위한 함수와 같다.
- 제네릭 타입은 타입스크립트에서 DRY 원칙을 적용하는 핵심 방법 중 하나이다. 제너릭을 사용하면 타입의 중복을 최소화하고 재사용성을 높일 수 있다.
ex )
function addNumbers(a: number, b: number): number {
return a + b;
}
function concatenateStrings(a: string, b: string): string {
return a + b;
}
두 함수는 각각 두개의 인수를 받아서 그 값을 합쳐서 반환하고있다.
이를 제너릭으로 변경할 수 있다.
function combine<T>(a: T, b: T): T {
return a + b;
}
combine 함수는 <T> 라는 타입의 매개변수를 사용한다.
`T` 는 함수를 호출할 때 전달된 값의 타입으로 결정된다.
따라서 이 함수는 어떤 타입의 값을 합쳐서 반환할 수 있게된다.
제너릭 타입에서는 매개변수를 제한할 수 있는 방법이 필요하다. (타입안전성을 보장하기 위해서)
제너릭 타입을 사용하면 여러 종류의 값들을 다룰 수 있는데, 제너릭 타입이 특정한 종류의 값만 다루도록 제한해야 하는 경우가 있다.
extends 를 이용하면 제너릭 매개변수가 특정 타입을 확장한다고 선언할 수 있고 이를 통해 매개변수를 제한할 수 있다.
interface Name{
first: string;
last: string;
}
type DancingDuo<T extends Name> = [T,T];
제네릭 타입 매개변수 T 는 Name을 확장한다.
DancingDuo 타입은 T 타입의 배열이고, 요소는 Name 타입 객체로 이뤄져있다.
const couple1: DancingDuo<Name> = [
{first: 'Fred', last: 'Astaire'},
{first: 'Ginger', last: 'Rogers'}
]; //정상
const couple2: DancingDuo<{first: string}> = [
{first: 'Sonny'},
{first: 'Cher'}
]; // 오류
오류메세지 : Name 타입에 필요한 last 속성이 {first: string;} 타입에 없습니다.
{first: string} 은 Name 을 확장하는 것이 아니기 때문에 오류가 발생한다.
Pick
K는 T타입과 무관하며 범위가 너무 넓다.
K는 keyof T 가 되어야 한다.
type Pick <T, K extends keyof T> = {
[k in K] : T[k]
};
T : 객체 타입
K : T의 속성 이름 중 일부를 선택할 수 있는 keyof 연산자를 사용한 타입 매개변수
Pick 타입은 T 타입에서 K 타입에 포함된 속성만 선택해 새로운 타입을 정의한다.
이 때 in 키워드를 사용한 매핑타입을 이용해서 새로운 객체 타입을 생성한다.
<참고>
[TS] 📘 타입스크립트 - 유틸리티 타입 💯 총정리 (+ 응용)
타입스크립트 - Utility Types 지금까지 타입스크립트를 다루면서, 자바스크립트를 어느정도 아니까 타입 종류만 배우면 뚝딱 마스터 할 줄 알았더니, 타입 자체를 코딩하며 에러줄을 사라지게 하
inpa.tistory.com
[TS] 📘 타입스크립트 - 조건부 타입 완벽 이해하기
고급 타입 - Conditional Types 조건부 타입(conditional type)이란 입력된 제네릭 타입에 따라 타입을 결정 기능을 말한다. 위와 같이 조건부 타입 문법은 extends 키워드와 물음표 ? 기호를 사용하는데, 보
inpa.tistory.com
'Typescript' 카테고리의 다른 글
[Typescript] 이펙티브 타입스크립트 2장 아이템17-18 (0) | 2023.03.07 |
---|---|
[Typescript] 이펙티브 타입스크립트 2장 아이템15-16 (0) | 2023.03.07 |
[Typescript] 이펙티브 타입스크립트 2장 아이템13 (0) | 2023.03.05 |
[Typescript] 이펙티브 타입스크립트 2장 아이템10-12 (0) | 2023.02.27 |
[Typescript] typeORM (0) | 2022.12.13 |