타입 단언
❗타입 단언을 사용하면 타입 체크를 할 수 없다.
타입 선언은 할당되는 값이 해당 인터페이스를 만족하는 검사하는데,
타입 단언은 강제로 타입을 지정했으니 타입 체커에게 오류를 무시하라고 하는 것
→ 타입 단언(as Type)보다는 타입 선언(: Type)을 사용하기
// 타입 단언 (Type assertion)
type Person = {
name: string;
age: number;
};
// name, age가 없어서 오류 발생
let person1: Person = {};
person1.name = "안수이";
person1.age = 24;
let person2 = {} as Person;
type Dog = {
name: string;
color: string;
};
let dog: Dog = {
name: "돌돌이",
color: "brown",
breed: "진도", // 오류 발생
} as Dog; // 오류 해결
// 타입 선언
const alice: Person = {
name: 'Alice',
age: 25,
};
// 타입 단언
const bob = {
name: 'Bob',
age: 20,
} as Person;
1️⃣ 타입 단언의 규칙
각 as 단언 ← 단언식 (A as B)
A가 B의 수퍼타입이거나 서브타입이어야 한다.
// 타입 단언의 규칙
let num1 = 10 as never; // 10이 never의 수퍼타입이므로 가능
let num2 = 10 as unknown; // 10이 never의 서브타입이므로 가능
let num3 = 10 as string; // 오류 발생
let num4 = 10 as unknown as string; // 오류 해결이지만 권장하지는 않는다 - 다중 단언
2️⃣ const 단언
리터럴로 취급. 즉 const로 선언한 것과 같이 만들어준다.
// const 단언
let num5 = 10 as const;
let cat = {
name: "애옹",
color: "yellow",
} as const; // 모든 프로퍼티가 readonly로 추론된다.
// 오류 발생 - 읽기 전용 속성이므로 바꿀 수 없다.
cat.name = "";
3️⃣ Non Null 단언
어떤 값이 null이거나 undefined이 아니라고 알려주는 역할
❗ Optional Chaining (?.)
?.은 ?.'앞’의 평가 대상이 undefined나 null이면 평가를 멈추고 undefined를 반환한다.
// Non Null 단언
type Post = {
title: string;
author?: string; // 익명도 가능하므로 ?를 붙여 선택적 프로퍼티로 설정
};
let post: Post = {
title: "게시글 1",
author: "안수이",
};
// author의 길이를 출력하여 len에 저장
// 옵셔널 체이닝: undefined이 될 수 있지만 number에 넣을 수 없기 때문에 오류 발생
const len1: number = post.author?.length;
// !를 넣으면 무조건 null, undefined이 아닐거라고 믿어서 오류가 발생하지 않는다.
// 그냥 그렇게 믿는 것이기 때문에 위험하다 !
const len2: number = post.author!.length;
타입 좁히기
조건문 등을 이용해 넓은 타입에서 좁은타입으로 타입을 상황에 따라 좁히는 방법
// 타입 좁히기
type Person = {
name: string;
age: number;
};
/*
value가 number이면 toFixed()
value가 string이면 toUpperCase()
value가 Date이면 getTime()
value가 Person이면 name은 age살입니다
*/
function func(value: number | string | Date | null | Person) {
// 타입에 따라 사용할 수 있는 함수가 다르므로 조건문 바깥에서는 오류 발생
value.toFixed();
value.toUpperCase();
// 변수가 특정 조건문 내부에서 더 좁은 타입임을 보장할 수 있으면 그렇게 추론한다.
if (typeof value === "number") {
console.log(value.toFixed());
} else if (typeof value === "string") {
console.log(value.toUpperCase());
} else if(typeof value === "object") {
console.log(value.getTime()); // object는 null도 가능하므로 오류 발생
} else if (value instanceof Date) {
// instanceof는 Date객체인지 묻고 맞으면 true 반환
console.log(value.getTime());
} else if (value && "age" in value) {
// instanceof는 우측에 타입이 오면 안된다.
// in 뒤에는 null, undefined가 오면 안된다. 따라서 &&를 사용해서 value가 있는 경우에만 사용
console.log(`${value.name}은 ${value.age}살 입니다.`);
}
}
서로소 유니온 타입
교집합이 없는 타입들로만 만든 유니온 타입
string 리터럴 타입을 넣어서 서로소 유니온 타입으로 만든다.
1️⃣ 예시 1
Admin → {name}님 현재까지 {kickCount}명 강퇴했습니다.
Member → {name}님 현재까지 {point}점 모았습니다.
Guest → {name}님 현재까지 {visitCount}번 방문하셨습니다.
// 서로소 유니온 타입
type Admin = {
tag: "ADMIN";
name: string;
kickCount: number; // 강퇴 회원 수
};
type Member = {
tag: "MEMBER";
name: string;
point: number;
};
type Guest = {
tag: "GUEST";
name: string;
visitCount: number; // 방문 횟수
};
type User = Admin | Member | Guest;
// 기존 코드 -> 주석이 없으면 누가 어떤 타입인지 알기 어렵다.
function login1(user: User) {
if ("kickCount" in user) {
// Admin 타입
console.log(`${user.name}님 현재까지 ${user.kickCount}명 강퇴했습니다.`);
} else if ("point" in user) {
// Member 타입
console.log(`${user.name}님 현재까지 ${user.point}점 모았습니다.`);
} else {
// Guest 타입
console.log(`${user.name}님 현재까지 ${user.visitCount}번 방문하셨습니다.`);
}
}
💡예시1) 서로소 유니온으로 해결
// 훨씬 직관적
// string 리터럴로 태그를 사용하면 무조건 타입이 좁혀진다.
function login2(user: User) {
if (user.tag === "ADMIN") {
// Admin 타입
console.log(`${user.name}님 현재까지 ${user.kickCount}명 강퇴했습니다.`);
} else if (user.tag === "MEMBER") {
// Member 타입
console.log(`${user.name}님 현재까지 ${user.point}점 모았습니다.`);
} else {
// Guest 타입
console.log(`${user.name}님 현재까지 ${user.visitCount}번 방문하셨습니다.`);
}
switch (user.tag) {
case "ADMIN":
console.log(`${user.name}님 현재까지 ${user.kickCount}명 강퇴했습니다.`);
break;
case "MEMBER":
console.log(`${user.name}님 현재까지 ${user.point}점 모았습니다.`);
break;
case "GUEST":
console.log(
`${user.name}님 현재까지 ${user.visitCount}번 방문하셨습니다.`
);
break;
}
}


2️⃣ 예시 2
로딩중 →콘솔에 로딩중 출력
실패 →실패: 에러메시지를 출력
성공 →성공: 데이터를 출력
// 예시 2
// 비동기 작업의 결과를 처리하는 객체
type LoadingTask = {
state: "LOADING";
};
type FailedTask = {
state: "FAILED";
error: {
message: string;
};
};
type SuccessTask = {
state: "SUCCESS";
response: {
data: string;
};
};
// 기존 방법
type AsyncTask = {
state: "LOADING" | "FAILED" | "SUCCESS";
error?: {
message: string;
};
response?: {
data: string;
};
};
function peocessResult(task: AsyncTask) {
switch (task.state) {
case "LOADING":
console.log("로딩 중");
break;
case "FAILED":
// ?를 지우면 오류 발생 -> 좁혀질 타입이 없다.
console.log(`에러 발생: ${task.error?.message}`);
break;
case "SUCCESS":
console.log(`성공: ${task.response?.data}`);
break;
}
}
💡예시2) 서로소 유니온으로 해결
// 예시2 해결
type LoadingTask = {
state: "LOADING";
};
type FailedTask = {
state: "FAILED";
error: {
message: string;
};
};
type SuccessTask = {
state: "SUCCESS";
response: {
data: string;
};
};
// 서로소 유니온 타입으로 정의
type AsyncTask = LoadingTask | FailedTask | SuccessTask;
function peocessResult(task: AsyncTask) {
switch (task.state) {
case "LOADING":
console.log("로딩 중");
break;
case "FAILED":
// ?.을 지워도 오류 발생 X
console.log(`에러 발생: ${task.error?.message}`);
break;
case "SUCCESS":
console.log(`성공: ${task.response?.data}`);
break;
}
}
이정환님의 인프런 강의 "한 입 크기로 잘라 먹는 타입스크립트(TypeScript)"를 참고하여 작성하였습니다.
'Web > TypeScript' 카테고리의 다른 글
[TS] 함수, 함수 타입 표현식과 호출 시그니처 (1) | 2024.11.19 |
---|---|
[TS] 타입스크립트 이해하기 - 대수 타입, 타입 추론 (1) | 2024.11.14 |
[TS] 타입스크립트 이해하기 - 타입 계층도, 타입 호환성 (0) | 2024.11.11 |
[TS] any 타입, unknown 타입, void 타입, never 타입 (1) | 2024.11.07 |
[TS] 타입 별칭과 인덱스 시그니처, Enum 타입 (0) | 2024.11.07 |