타입스크립트에서 특정 타입의 값을 다른 타입의 값으로 취급해도 괜찮은지 판단하는 기준
number - number 리터럴
타입의 값이 있을 때 이 타입들은 서로 호환이 될까?
그렇지 않다. number는 number 리터럴 타입 값이 될 수 있지만, number 리터럴 타입은 number 타입이 될 수 없다. 타입스크립트는 업 캐스팅은 가능하지만, 다운 캐스팅은 불가능하다고 판단한다.
아래의 예를 참고했을 때 모든 강아지는 동물이 될 수 있지만(업 캐스팅), 모든 동물은 강아지가 될 수 없는 것과 같다.(다운 캐스팅)
또 다른 예시를 보자. a는 number 타입이고, b는 number 리터럴 타입이다. a가 b의 슈퍼 타입이 된다. 고로 업 캐스팅인 a = b
는 가능하지만, 다운 캐스팅인 b = a
는 불가능하다.
let a: number = 12;
let b: 30 = 30;
a = b; // 호환 O, 업 캐스팅
b = a; // 호환 X, 다운 캐스팅
unknown은 최상단에 위치하므로 모든 타입의 슈퍼 타입이라고 할 수 있다. 그렇기 때문에 모든 타입의 값을 저장할 수 있다.
let a: unknown = 123;
let b: unknown = "hello world";
let c: unknown = true;
let d: unknown = null;
let e: unknown = undefined;
let f: unknown = [];
let g: unknown = {};
let h: unknown = () => {};
let unknownVar: unknown;
let num: number = unknownVar;
let str: string = unknownVar;
let bool: boolean = unknownVar;
foo라는 함수에서는 매개 변수 value
를 unknown 타입으로 받고 있다. unknown은 최상위에 위치하고 있기 때문에 어떠한 타입이라도 다 들어올 수 있게 된다. 그렇지만 안의 조건문을 통해서 타입 좁히기를 할 수 있다. value
가 number인 경우에 실행할 로직, string일 경우에 실행할 로직을 따로 작성하면 특정 타입으로 좁혀 원하는 동작을 실행시킬 수 있다.
function foo(value: unknown) {
if (typeof value === "number") {
return value.toFixed();
} else if (typeof value === "string") {
return value.toLocaleLowerCase();
} else {
return value;
}
}
foo(1);
foo("hello");
foo(undefined);
Never는 unknown과 정반대의 특징을 가진다. 최하위 계층에 위치하기 때문에 모든 타입의 서브 타입이다.
let neverVar: never;
let num: number = neverVar;
let str: string = neverVar;
let bool: boolean = neverVar;
let a: never = 123;
let b: never = "hello world";
let c: never = true;
let d: never = null;
let e: never = undefined;
let f: never = [];
let g: never = {};
let h: never = () => {};
value
는 never 타입을 가지기 때문에 어떠한 값도 넣을 수 없게 된다.function error(value: never) {
throw new Error();
}
error(); // 인수 없음, 오류
error(1); // 불가
error("hello"); // 불가
error(undefined); // 불가
function error(value: never) {
throw new Error();
}
type Color = "RED" | "GREEN" | "BLUE"
function getColorName(color: Color) {
switch(color) {
case "RED":
return "rgb(255, 0, 0)";
case "GREEN":
return "rgb(0, 255, 0)";
default: {
return error(color);
}
}
}
any는 타입 검사를 받지 않는다. unknown 타입처럼 모든 타입의 값을 다 저장할 수 있고, never처럼 어느 타입의 변수든 저장될 수 있기도 하다. 모든 타입의 슈퍼 타입이 되기도 하고, 서브 타입이 되기도 한다. 그래서 any는 치트키 역할이라고 할 수 있다. 그러나 딱 한 가지 예외가 있다. never 타입에는 any를 넣을 수 없다. never는 공집합이기 때문에 어떤 값도 포함할 수 없기 때문이다.
let anyVar: any;
// 어느 타입이든 저장할 수 있음
anyVar = 2023;
anyVar = "hello world!";
anyVar = true;
anyVar = undefined;
anyVar = null;
// 어느 타입이든 저장될 수 있음
let num: number = anyVar;
let str: string = anyVar;
let bool: boolean = anyVar;
let _null: null = anyVar;
let _unde: undefined = anyVar;
str1: number → string으로의 단언은 불가능하다.
슈퍼 - 서브
타입 관계를 가져야 한다.str2: number → any → string의 형식으로 단언은 가능하다.
str3: number → unknown → string
let str1: string = 10 as string; // 오류
let str2: string = 10 as any as string; // 실행
let str3: string = 10 as unknown as string; // 실행
🔗 참고
인프콘 2023 다시보기
타입 단언 | 타입스크립트 핸드북
한 입 크기로 잘라먹는 타입스크립트 - 타입 계층도와 함께 기본 타입 살펴보기