TypeScript에서 타입은 집합으로 이해할 수 있다.
예를 들어, number 타입은 모든 숫자를 포함하는 슈퍼 타입이고, 10 같은 리터럴 타입은 특정 숫자만 포함하는 서브 타입이다.
let num1: number = 10; // number 타입 (슈퍼 타입)
let num2: 10 = 10; // number 리터럴 타입 (서브 타입)
타입 호환성이란 어떤 타입을 다른 타입으로 취급해도 괜찮은지 판단하는 것을 의미한다.
TypeScript에서는 구조적 타이핑(structural typing)을 기반으로 타입 호환성을 결정한다.
서브 타입을 슈퍼 타입에 할당하는 것은 허용된다.
let num1: number = 10;
let num2: 10 = 10;
num1 = num2; // ✅ 문제없음 (업 캐스팅)
슈퍼 타입을 서브 타입에 할당하는 것은 허용되지 않는다.
let num1: number = 10;
let num2: 10 = 10;
num2 = num1; // ❌ 오류 발생 (다운 캐스팅 불가)
TypeScript의 주요 타입 계층 구조는 다음과 같다.

any는 모든 타입과 호환되지만, unknown은 타입을 확정하지 않으면 사용할 수 없다.any | unknown | object | void | undefined | null | never | |
|---|---|---|---|---|---|---|---|
any | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | |
unknown | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | |
object | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | |
void | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | |
undefined | ✅ | ✅ | ☑️ | ✅ | ☑️ | ❌ | |
null | ✅ | ✅ | ☑️ | ☑️ | ☑️ | ❌ | |
never | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
let num1: number = 10;
let num2: 10 = 10;
num1 = num2; // 업 캐스팅
객체 타입도 마찬가지로 슈퍼 타입과 서브 타입 간의 관계가 성립한다.
// 슈퍼 타입
type Animal = {
name: string;
color: string;
};
// 서브 타입
type Dog = {
name: string;
color: string;
breed: string;
}
let animal: Animal = {
name: "기린",
color: "yellow",
}
let dog: Dog = {
name: "꽝꽝이",
color: "white",
breed: "스피츠",
}
// 업 캐스팅
animal = dog;
// 다운 캐스팅
dog = animal; // ❌ 오류 발생
// 슈퍼 타입
type Book = {
name: string;
price: number;
};
// 서브 타입
type ProgrammingBook = {
name: string;
price: number;
skill: string;
};
let book: Book;
let programmingBook: ProgrammingBook = {
name: "한 입 크기로 잘라먹는 리액트",
price: 33000,
skill: "react.js",
};
// 업 캐스팅
book = programmingBook; // ✅ 문제없음 (업 캐스팅)
// 다운 캐스팅
programmingBook = book; // ❌ 오류 발생
// ✅ 초과 프로퍼티 검사
let book2: Book = {
name: "한 입 크기로 잘라먹는 리액트",
price: 33000,
skill: "react.js", // ❌ 오류 발생 (초과 프로퍼티 검사)
};
// 하지만 변수에 저장한 후 전달하면 오류가 발생하지 않는다.
let book3: Book = programmingBook; // ✅ 문제없음
function func(book: Book) {}
func({
name: "한 입 크기로 잘라먹는 리액트",
price: 33000,
skill: "react.js", // ❌ 오류 발생 (초과 프로퍼티 검사)
});
func(programmingBook);