[Typescript] 고급 타입 한번에 보기

mainsain·2024년 2월 18일
0

Typescript

목록 보기
6/8
post-thumbnail

타입 용어정리

1️⃣ ] any 타입, unknown 타입

  • any : JS에 존재하는 모든 값을 오류 없이 받아올 수 있다.
  • unknown : any와 유사하게 모든 타입의 값이 할당될 수 있다.
    • any타입 외에 다른 타입으로 할당 불가능

any vs unknown 정리 ← 자세한 건 여기에 정리해뒀다.

2️⃣ ] void 타입

function showModal(type: ModalType): void {
  feedbackSlice.actions.createModal(type);
}
  
// 화살표 함수로 작성 시
const showModal = (type: ModalType): void => {
  feedbackSlice.actions.createModal(type);
};

타입스크립트에서 함수가 어떤 값을 반환하지 않는 경우 void를 지정하여 사용한다.

  • 변수에도 할당이 가능하지만, 함수가 아니면 대부분 무의미하다.
    • void 타입 변수는 undefined, null 값만 할당가능하기 때문이다.
    • 차라리 undefined, null을 사용하는게 명시적인 의미부여가 가능하다.

3️⃣ ] never 타입

값을 반환할 수 없는 타입을 말하며, 일반적으로 함수와 관련해서 주로 사용된다.

반환하지 않는 것과 구분해야한다. JS에서 값을 반환할 수 없는 경우는 2가지로 나눌 수 있다.

1️⃣ ] 에러를 던지는 경우

function generateError(res: Response): never {
  throw new Error(res.getMessage());
}

JS에선 런타임에 throw 키워드를 사용해 의도적으로 에러를 발생시키고 캐치할 수 있다.
이는 값을 반환하지 않는것으로 간주하기에, 에러를 던지는 작업을 한다면 해당 함수의 반환 타입은 never이다.

2️⃣ ] 무한히 함수가 실행되는 경우

function checkStatus(): never {
  while (true) {
    // ...
  }
}

함수가 종료되지 않기에 값을 반환하지 못한다.

4️⃣ ] Array 타입

const fn = () => console.log(1);
const array = [1, "string", fn]; // 자바스크립트에서는 배열에 숫자, 문자열, 함수 등 다양한 값을 삽입할 수 있다

array[0]; // 1
array[1]; // string
array[2](); // 1
// 자바스크립트의 경우
String[] array = { "string1", "string2", "string" };
// String 타입의 배열로 선언된 array에 int, float 같은 다른 자료형의 원소는 허용하지 않는다
// 자바의 경우
  • 엄밀히 말하면 JS에서의 배열은, 객체에 속하는 타입으로 분류한다. 즉, JS 배열 !== Array
  • 따라서 TS에서 배열을 사용하기 위해 특수한 문법을 함께 다뤄야 한다.

TS에서 배열타입을 선언할 수 있는 두가지 방법

const array: number[] = [1, 2, 3]; // 숫자에 해당하는 원소만 허용한다
const array: Array<number> = [1, 2, 3];
// number[]와 동일한 타입이다

두 방식의 차이점은, 선언하는 형식 외에는 없기에 컨벤션을 맞춰도 되고 혼용해도 된다.

다양한 자료형을 모두 관리하는 배열을 사용하는 방법

const array1: Array<number | string> = [1, "string"];
const array2: number[] | string[] = [1, "string"];

// 후자의 방식은 아래와 같이 선언할 수도 있다
const array3: (number | string)[] = [1, "string"];

이처럼 유니온타입을 활용해 사용할 수 있다.

그러나 TS에서 배열 타입을 명시하는 것으로 강제했지만, 배열의 길이까지 제한할 수 없다. 여기에서 튜플이 등장했다.

튜플 (tuple) : 길이 제한까지 추가한 타입 시스템

let tuple: [number] = [1];
tuple = [1, 2]; // 불가능
tuple = [1, "string"]; // 불가능

let tuple: [number, string, boolean] = [1, "string", true]; // 여러 타입과 혼합도 가능하다
  • 길이까지 제한시켜서 원소 개수와 타입을 보장한다.
  • JS의 동적 자유로움으로 인한 런타임 에러와 유지보수의 어려움을 막기 위한 것

또한 옵셔널(optional) 프로퍼티를 활용하여 선택적으로 사용할 수도 있다.

const optionalTuple1: [number, number, number?] = [1, 2];
const optionalTuple2: [number, number, number?] = [1, 2, 3]; // 3번째 인덱스에 해당하는 숫자형 원소는 있어도 되고 없어도 됨을 의미한다

옵셔널 : 특정 속성 또는 매개변수가 값이 있을 수도 있고 없을 수도 있는 것을 의미한다.

5️⃣ ] enum 타입

열거형이라고도 부른다.

[Typescript] 고급 타입 - enum ← 자세한 건 여기에 정리해뒀다.


타입 조합

앞에서 다룬 개념들을 응용하거나 추가해 심화된 타입 검사 방법을 알아보자.

1️⃣ ] 교차 타입 (Intersection)

여러가지 타입을 결합하여 하나의 단일 타입으로 만들 수 있다.

  • &를 사용해서 표기한다.
  • 결과물로 탄생한 단일 타입에는 타입 별칭(type alias)를 붙일 수도 있다.
type ProductItem = {
  id: number;
  name: string;
  type: string;
  price: number;
  imageUrl: string;
  quantity: number;
};

type ProductItemWithDiscount = ProductItem & { discountAmount: number };

A & B라면 C는 A와 B의 모든 멤버를 가지고 있어야 한다.

2️⃣ ] 유니온 타입 (Union)

타입 A 또는 타입 B 중 하나가 될 수 있는 타입

  • ‘|’ 를 사용하여 표기한다.
  • 주로 특정 변수가 가질 수 있는 타입을 전부 나열하는 용도로 사용한다.
  • 교차 타입과 마찬가지로 타입을 이어붙일수도 있고, 타입 별칭을 통해 중복을 줄일 수도 있다.
type CardItem = {
  id: number;
  name: string;
  type: string;
  imageUrl: string;
};

type PromotionEventItem = ProductItem | CardItem;

const printPromotionItem = (item: PromotionEventItem) => {
  console.log(item.name); // O
  console.log(item.quantity); // 컴파일 에러 발생
};

위 예시의 경우, 함수 CardItemquantity가 없기에 컴파일 에러가 발생한다. 따라서 유니온 타입에 포함된 모든 타입이 공통으로 갖고 있는 속성에만 접근해야 한다.
item이라는 유니온 타입은 Product 또는 CardItem일 뿐이지 Product이면서 CardItem인 것은 아니다.

3️⃣ ] 인덱스 시그니처 (Index Signatures)

특정 타입의 속성 이름은 알 수 없지만, 속성값의 타입을 알고 있을 때 사용한다.

interface IndexSignatureEx {
  [key: string]: number;
}

위 예시에선, 해당 타입의 속성 키는 모두 string이어야 하고, 속성값은 number 타입을 가져야한다는 의미이다.

interface IndexSignatureEx2 {
  [key: string]: number | boolean;
  length: number;
  isValid: boolean;
  name: string; // 에러 발생
}

위 예시에서 name은 string 타입을 갖도록 선언되어 있지만, 인덱스 시그니처의 키가 string일 때는 number | boolean 타입이여만 하므로 에러가 발생한다.

4️⃣ ] 인덱스드 엑세스 타입 (Indexed Access Types)

다른 타입의 특정 속성이 가진 타입을 조회하기 위해 사용한다.

type Example = {
  a: number;
  b: string;
  c: boolean;
};

type IndexedAccess = Example["a"];
type IndexedAccess2 = Example["a" | "b"]; // number | string
type IndexedAccess3 = Example[keyof Example]; // number | string | boolean

type ExAlias = "b" | "c";
type IndexedAccess4 = Example[ExAlias]; // string | boolean

위 예시에서 IndexedAccess는 Example타입의 특정 속성이 가지는 타입을 조회하기 위한 타입이다.

물론 이 또한 타입이기에 유니온, keyof, 타입 별칭 등 키워드를 사용할 수 있다.

5️⃣ ] 맵드 타입 (Mapped Types)

JS의 map 메서드처럼, 기존의 타입을 기반으로 새로운 타입을 선언할 때 사용하는 문법이다.

  • 인덱스 시그니처 문법을 사용해 반복적인 타입 선언을 효과적으로 줄일 수 있다.
type Example = {
  a: number;
  b: string;
  c: boolean;
};

type Subset<T> = {
  [K in keyof T]?: T[K];
};

const aExample: Subset<Example> = { a: 3 };
const bExample: Subset<Example> = { b: "hello" };
const acExample: Subset<Example> = { a: 4, c: true };

Subset의 맵드타입은, 제네릭 타입 T를 받아 keyof T를 순회하며 각 속성 K를 선택적?으로 만들고, 해당 타입을 T[K] (원본 타입 T에서 속성 K의 타입)으로 지정한다.

배달의 민족 선물하기 서비스 예제

const BottomSheetMap = {
  RECENT_CONTACTS: RecentContactsBottomSheet,
  CARD_SELECT: CardSelectBottomSheet,
  SORT_FILTER: SortFilterBottomSheet,
  PRODUCT_SELECT: ProductSelectBottomSheet,
  REPLY_CARD_SELECT: ReplyCardSelectBottomSheet,
  RESEND: ResendBottomSheet,
  STICKER: StickerBottomSheet,
  BASE: null,
};

export type BOTTOM_SHEET_ID = keyof typeof BottomSheetMap;

// 불필요한 반복이 발생한다
type BottomSheetStore = {
  RECENT_CONTACTS: {
    resolver?: (payload: any) => void;
    args?: any;
    isOpened: boolean;
  };
  CARD_SELECT: {
    resolver?: (payload: any) => void;
    args?: any;
    isOpened: boolean;
  };
  SORT_FILTER: {
    resolver?: (payload: any) => void;
    args?: any;
    isOpened: boolean;
  };
  // ...
};

// Mapped Types를 통해 효율적으로 타입을 선언할 수 있다
type BottomSheetStore = {
  [index in BOTTOM_SHEET_ID]: {
    resolver?: (payload: any) => void;
    args?: any;
    isOpened: boolean;
  };
};

BOTTOM_SHEET_ID를 분리하고, 맵드타입을 활용해 각 키에 해당하는 스토어를 선언해 효과적으로 코드를 줄일 수 있다.

type BottomSheetStore = {
  [index in BOTTOM_SHEET_ID as `${index}_BOTTOM_SHEET`]: {
    resolver?: (payload: any) => void;
    args?: any;
    isOpened: boolean;
  };
};

덧붙여 맵드 타입에선 as 키워드를 사용해 키를 재지정할 수 있다.

6️⃣ ] 템플릿 리터럴 타입 (Template Literal Types)

자바스크립트의 템플릿 리터럴 문자열을 사용해 문자열 리터럴 타입을 선언할 수 있는 문법이다.

맵드타입의 BottomSheetMap의 각 키마다 _BOTTOM_SHEET를 붙여주는 예시가 이를 활용한 것

type Stage =
  | "init"
  | "select-image"
  | "edit-image"
  | "decorate-card"
  | "capture-image";
type StageName = `${Stage}-stage`;
// ‘init-stage’ | ‘select-image-stage’ | ‘edit-image-stage’ | ‘decorate-card-stage’ | ‘capture-image-stage’

Stage 타입의 모든 유니온 멤버 뒤에 -stage를 붙여 새로운 문자열 리터럴 유니온 타입을 만들었다.

7️⃣ ] 제네릭 (Generic)

다양한 타입을 유연하게 사용하기 위해 존재하며, 특정 태그를 선언해두고 실제 이용할 때 타입을 지정해 사용하는 방식이다.

[Typescript] Generic ← 자세히 정리해놨다.

profile
새로운 자극을 주세요.

0개의 댓글