any
: JS에 존재하는 모든 값을 오류 없이 받아올 수 있다.unknown
: any와 유사하게 모든 타입의 값이 할당될 수 있다.any vs unknown 정리 ← 자세한 건 여기에 정리해뒀다.
function showModal(type: ModalType): void {
feedbackSlice.actions.createModal(type);
}
// 화살표 함수로 작성 시
const showModal = (type: ModalType): void => {
feedbackSlice.actions.createModal(type);
};
타입스크립트에서 함수가 어떤 값을 반환하지 않는 경우 void를 지정하여 사용한다.
값을 반환할 수 없는 타입을 말하며, 일반적으로 함수와 관련해서 주로 사용된다.
반환하지 않는 것과 구분해야한다. JS에서 값을 반환할 수 없는 경우는 2가지로 나눌 수 있다.
function generateError(res: Response): never {
throw new Error(res.getMessage());
}
JS에선 런타임에 throw
키워드를 사용해 의도적으로 에러를 발생시키고 캐치할 수 있다.
이는 값을 반환하지 않는것으로 간주하기에, 에러를 던지는 작업을 한다면 해당 함수의 반환 타입은 never
이다.
function checkStatus(): never {
while (true) {
// ...
}
}
함수가 종료되지 않기에 값을 반환하지 못한다.
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 같은 다른 자료형의 원소는 허용하지 않는다
// 자바의 경우
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에서 배열 타입을 명시하는 것으로 강제했지만, 배열의 길이까지 제한할 수 없다. 여기에서 튜플이 등장했다.
let tuple: [number] = [1];
tuple = [1, 2]; // 불가능
tuple = [1, "string"]; // 불가능
let tuple: [number, string, boolean] = [1, "string", true]; // 여러 타입과 혼합도 가능하다
또한 옵셔널(optional) 프로퍼티를 활용하여 선택적으로 사용할 수도 있다.
const optionalTuple1: [number, number, number?] = [1, 2];
const optionalTuple2: [number, number, number?] = [1, 2, 3]; // 3번째 인덱스에 해당하는 숫자형 원소는 있어도 되고 없어도 됨을 의미한다
옵셔널 : 특정 속성 또는 매개변수가 값이 있을 수도 있고 없을 수도 있는 것을 의미한다.
열거형이라고도 부른다.
[Typescript] 고급 타입 - enum ← 자세한 건 여기에 정리해뒀다.
앞에서 다룬 개념들을 응용하거나 추가해 심화된 타입 검사 방법을 알아보자.
여러가지 타입을 결합하여 하나의 단일 타입으로 만들 수 있다.
type ProductItem = {
id: number;
name: string;
type: string;
price: number;
imageUrl: string;
quantity: number;
};
type ProductItemWithDiscount = ProductItem & { discountAmount: number };
A & B라면 C는 A와 B의 모든 멤버를 가지고 있어야 한다.
타입 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); // 컴파일 에러 발생
};
위 예시의 경우, 함수 CardItem
엔 quantity
가 없기에 컴파일 에러가 발생한다. 따라서 유니온 타입에 포함된 모든 타입이 공통으로 갖고 있는 속성에만 접근해야 한다.
item
이라는 유니온 타입은 Product
또는 CardItem
일 뿐이지 Product
이면서 CardItem
인 것은 아니다.
특정 타입의 속성 이름은 알 수 없지만, 속성값의 타입을 알고 있을 때 사용한다.
interface IndexSignatureEx {
[key: string]: number;
}
위 예시에선, 해당 타입의 속성 키는 모두 string이어야 하고, 속성값은 number 타입을 가져야한다는 의미이다.
interface IndexSignatureEx2 {
[key: string]: number | boolean;
length: number;
isValid: boolean;
name: string; // 에러 발생
}
위 예시에서 name은 string 타입을 갖도록 선언되어 있지만, 인덱스 시그니처의 키가 string일 때는 number | boolean 타입이여만 하므로 에러가 발생한다.
다른 타입의 특정 속성이 가진 타입을 조회하기 위해 사용한다.
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, 타입 별칭 등 키워드를 사용할 수 있다.
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
키워드를 사용해 키를 재지정할 수 있다.
자바스크립트의 템플릿 리터럴 문자열을 사용해 문자열 리터럴 타입을 선언할 수 있는 문법이다.
맵드타입의 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
를 붙여 새로운 문자열 리터럴 유니온 타입을 만들었다.
다양한 타입을 유연하게 사용하기 위해 존재하며, 특정 태그를 선언해두고 실제 이용할 때 타입을 지정해 사용하는 방식이다.
[Typescript] Generic ← 자세히 정리해놨다.