목차
TypeScript 타입 시스템: 유니언과 리터럴
TypeScript는 JavaScript 값에 대한 정적 검사를 더한다. string, number, boolean, bigint, symbol, null, undefined 같은 원시 값의 타입을 표현하고, 객체와 함수의 모양도 검사한다. 검사 결과는 개발 중 오류를 일찍 찾는 데 쓰이지만 컴파일 뒤 타입 선언은 사라진다. 서버 응답이나 사용자 입력이 실제로 선언한 타입인지 자동으로 검증해 주지는 않는다.
추론과 명시적 타입
let singer = "Ella"; // string으로 추론
singer = "Nina";
// singer = 19; // 컴파일 오류
let selected: string | null = null;
selected = "Ella";
초기값이 있으면 보통 타입을 추론하므로 모든 변수에 애너테이션을 붙일 필요는 없다. selected는 null과 문자열을 모두 허용해야 하므로 string | null을 명시한다. let value;처럼 초기값 없이 선언한 변수는 검사 설정과 이후 사용에 따라 타입이 넓어질 수 있어, 공개 API나 중요한 상태라면 의도한 타입을 명시하는 편이 낫다.
console.logs(...)처럼 없는 속성을 호출하면 타입 검사 오류가 난다. 문법 오류와 타입 오류는 다르다. 타입 오류가 있어도 설정에 따라 JavaScript 출력이 만들어질 수 있으므로 배포 단계에서는 타입 검사 명령의 종료 코드를 확인한다.
유니언 타입은 사용 전 좁힌다
function describe(value: string | number): string {
if (typeof value === "string") {
return value.toUpperCase();
}
return value.toFixed(1);
}
console.log(describe("hello")); // HELLO
console.log(describe(3)); // 3.0
string | number에는 두 타입의 공통으로 안전한 연산만 바로 쓸 수 있다. typeof 검사 뒤 참인 가지에서는 문자열, 다른 가지에서는 숫자로 좁혀진다. 이것이 내로잉(narrowing)이다. typeof null은 "object"이므로 객체 여부를 검사할 때는 value !== null도 함께 확인한다. 선언한 유니언 타입은 모든 입력 값이 올바르다고 보장하는 런타임 검증은 아니다.
리터럴 타입과 판별 유니언
type State = "idle" | "loading" | "done";
let state: State = "idle";
state = "loading";
// state = "finished"; // 컴파일 오류
type Result =
| { kind: "success"; value: string }
| { kind: "failure"; message: string };
function show(result: Result): string {
if (result.kind === "success") return result.value;
return `오류: ${result.message}`;
}
State는 아무 문자열이 아니라 정해진 세 값만 허용한다. Result는 kind에 따라 필요한 필드가 달라진다. kind === "success" 검사 뒤에는 value가 있다는 사실을 컴파일러가 안다. 한 객체 타입에서 value?와 message?를 모두 선택적으로 선언하면 성공인데 값이 빠진 모순된 상태를 허용할 수 있으므로 판별 유니언이 더 정확하다.
배열·객체의 묶임을 읽기
let textOrNumbers: string | number[] = "ready";
textOrNumbers = [1, 2];
let mixed: (string | number)[] = ["A", 2];
type Poet = { born: number; name: string };
const poet: Poet = { born: 1935, name: "Sara" };
괄호 위치에 따라 string | number[]는 문자열 하나 또는 숫자 배열, (string | number)[]는 여러 원소가 각각 문자열이나 숫자일 수 있는 배열이다. TypeScript의 객체 타입은 이름이 같아야 하는 명목 타입보다는 필요한 구조가 있는지를 비교한다. 그래서 { born, name, extra } 값을 먼저 변수에 담았다면 Poet가 필요한 곳에 전달할 수 있다. 그러나 외부 JSON에서 두 필드의 실제 타입을 확인하려면 typeof 등의 런타임 검증을 별도로 작성한다.