•
변수 또는 표현식의 타입 범위를 더 작은 범위로 좁혀나가는 과정
◦
더 정확하고 명시적인 타입 추론 가능
◦
복잡한 타입을 작은 범위로 축소하여 타입 안정성을 높힘
•
타입 좁히기의 5가지 방법
◦
타입가드
◦
typeof
◦
instanceof
◦
in
◦
is
1. 타입 가드에 따라 분기 처리하기
•
런타임에 조건문을 사용해 타입 검사 → 타입 범위를 좁혀주는 기능
•
예시
◦
여러 타입 할당 가능한 스코프에서 특정 타입에 대한 분기 처리
◦
컴파일 시 타입 정보는 제거 되기에, 컴파일해도 사라지지 않는 방법 필요
1-1. 자바스크립트 연산자를 활용한 타입가드
•
제어문을 통해 특정 값을 가질 수 밖에 없는 상황 유도
•
런타임에 유효한 타입 가드를 만들 때 유용함
1-1-1. 원시 타입을 추론할 때: typeof 연산자 활용하기
•
typeof 연산자는 값의 타입 추론 가능
•
복잡한 타입 검증에는 한계 (null, 배열 등이 object로 판별하는 등)
•
그래서 주로 원시 타입을 좁히는데에 유용
1-1-2. 인스턴스화된 객체 타입을 판별할 때: instanceof 연산자 활용하기
A instanceof B // A에는 타입을 검사할 대상 변수, B에는 특정 객체의 생성자 (B가 상위 개념)
// A의 프로토타입 체인에 생성자 B가 존재하면 true
// A의 프로토타입 체인에 생성자 B가 존재하지 않으면 false
TypeScript
복사
프로토타입 속성 변화에 따라 instanceof 연산자의 결과가 달라질 수 있음
1-1-3. 객체의 속성이 있는지 없는지에 따른 구분: in 연산자 활용하기
A in B // A 라는 속성이 B 객체에 존재하는지 검사
TypeScript
복사
속성의 값이 falsy해도 속성 자체가 존재한다면 결과값은 true가 됨
1-2. is 연산자로 사용자 정의 타입 가드 만들어 활용하기
•
반환 타입이 타입 명제(type predicates)인 함수를 정의해서 사용
const isDestinationCode = (x: string): boolean =>
destinationCodeList.includes(x);
const getAvailableDestinationNameList = async (): Promise<DestinationName[]> => {
const data = await AxiosRequest<string[]>(“get”, “.../destinations”);
const destinationNames: DestinationName[] = [];
data?.forEach((str) => {
// isDestinationCode의 반환값이
// 1. boolean일 경우 -> str이 단순 string으로 추론 됨
// 2. is 연산자일 경우 -> str이 destinationCodeList의 값 중 하나로 추론 됨
if (isDestinationCode(str)) { // str이 destinationCodeList의 원소가 맞는지 체크.
destinationNames.push(DestinationNameSet[str]); // 맞다면 DestinationNames 배열에 push.
/*
isDestinationCode의 반환 값에 is를 사용하지 않고 boolean이라고 한다면 다음 에러가
발생한다
- Element implicitly has an ‘any’ type because expression of type ‘string’
can’t be used to index type ‘Record<”MESSAGE_PLATFORM” | “COUPON_PLATFORM” | “BRAZE”,
// string[] 타입인 str을 DestinationName[]에 push할 수 없다는 에러
“통합메시지플랫폼” | “쿠폰대장간” | “braze”>’
*/
}
});
return destinationNames;
};
TypeScript
복사
1-3. 식별할 수 있는 유니온
•
비슷하지만 다른 타입이 호환되지 않도록 판별자가 되는 속성을 추가
•
태그된 유니온(tagged union) 또는 식별할 수 있는 유니온(discriminated union)으로 불림
•
예시
type TextError = {
errorCode: string;
errorMessage: string;
};
type ToastError = {
errorCode: string;
errorMessage: string;
toastShowDuration: number; // 토스트를 띄워줄 시간
};
type AlertError = {
errorCode: string;
errorMessage: string;
onConfirm: () => void; // 얼럿 창의 확인 버튼을 누른 뒤 액션
};
type ErrorFeedbackType = TextError | ToastError | AlertError;
const errorArr: ErrorFeedbackType[] = [
{
errorCode: "999",
errorMessage: "잘못된 에러",
// 실제로 아래 두 개의 추가 속성을 가지는 타입을 없으나 덕타이핑으로 인해 TS에서는 가능
toastShowDuration: 3000,
onConfirm: () => {},
},
];
TypeScript
복사
•
해결책
type ErrorFeedbackType = TextError | ToastError | AlertError;
const errorArr: ErrorFeedbackType[] = [
{ errorType: “TEXT”, errorCode: “100”, errorMessage: “텍스트 에러” },
{
errorType: “TOAST”,
errorCode: “200”,
errorMessage: “토스트 에러”,
toastShowDuration: 3000,
},
{
errorType: “ALERT”,
errorCode: “300”,
errorMessage: “얼럿 에러”,
onConfirm: () => {},
},
{
errorType: “TEXT”,
errorCode: “999”,
errorMessage: “잘못된 에러”,
toastShowDuration: 3000, // Object literal may only specify known properties, and ‘toastShowDuration’ does not exist in type ‘TextError’
onConfirm: () => {},
},
{
errorType: “TOAST”,
errorCode: “210”,
errorMessage: “토스트 에러”,
onConfirm: () => {}, // Object literal may only specify known properties, and ‘onConfirm’ does not exist in type ‘ToastError’
},
{
errorType: “ALERT”,
errorCode: “310”,
errorMessage: “얼럿 에러”,
toastShowDuration: 5000, // Object literal may only specify known properties, and ‘toastShowDuration’ does not exist in type ‘AlertError’
},
];
TypeScript
복사
•
식별할 수 있는 유니온의 판별자 선정
◦
반드시 판별자가 유닛타입으로 선언되어야 정상동작
▪
유닛타입 - 다른 타입으로 쪼개지지 않고 오직 하나의 정확한 값을 가지는 타입
•
O - ull, undefined, 리터럴 타입을 비롯해 true, 1
•
X - void, string, number 등
1-3. Exhaustiveness Checking 으로 정확한 타입 분기 유지
•
일반적으로는 필요한 케이스에 대한 타입 분기 처리를 하면 요구 사항 충족
•
그러나, 모든 케이스에 대해 분기 처리를 해야 유지보수 시 안전하다 느끼는 경우
•
예시
◦
상품권 가격별 이름을 반환하는 로직
type ProductPrice = “10000” | “20000”;
const getProductName = (productPrice: ProductPrice): string => {
if (productPrice === “10000”) return “배민상품권 1만 원”;
if (productPrice === “20000”) return “배민상품권 2만 원”;
else {
return “배민상품권”;
}
};
TypeScript
복사
•
케이스가 추가되면 ProductPrice과 getProductName를 동시 수정해야하나
•
휴먼에러로 인해 진행되지 않으면 에러의 가능성
type ProductPrice = “10000” | “20000” | “5000”;
const getProductName = (productPrice: ProductPrice): string => {
if (productPrice === “10000”) return “배민상품권 1만 원”;
if (productPrice === “20000”) return “배민상품권 2만 원”;
if (productPrice === “5000”) return “배민상품권 5천 원”; // 조건 추가 필요
else {
return “배민상품권”;
}
};
TypeScript
복사
•
Exhaustiveness Checking 이용 시 안전하게 컴파일 타임에 확인 가능
type ProductPrice = "10000" | "20000" | "5000";
const getProductName = (productPrice: ProductPrice): string => {
if (productPrice === "10000") return "배민상품권 1만 원";
if (productPrice === "20000") return "배민상품권 2만 원";
if (productPrice === "5000") return "배민상품권 5천 원";
else {
exhaustiveCheck(productPrice); // productPrice의 type은 never로 추론 됨
return "배민상품권";
}
};
const exhaustiveCheck = (param: never) => {
throw new Error("type error!");
};
TypeScript
복사
type ProductPrice = “10000” | “20000” | “5000”;
const getProductName = (productPrice: ProductPrice): string => {
if (productPrice === “10000”) return “배민상품권 1만 원”;
if (productPrice === “20000”) return “배민상품권 2만 원”;
// if (productPrice === “5000”) return “배민상품권 5천 원”;
else {
exhaustiveCheck(productPrice);
// Error: Argument of type ‘string’ is not assignable to parameter of type ‘never’
return “배민상품권”;
}
};
const exhaustiveCheck = (param: never) => {
throw new Error(“type error!”);
};
TypeScript
복사