
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 ← 자세히 정리해놨다.