출시·고도화 중
TypeScript 안내서 · 3/6
이 장에서는 TypeScript로 데이터를 표현하는 기본 도구를 살펴봅니다. 원시 타입부터 배열과 튜플, 객체, 인터페이스와 타입 별칭, 유니언과 리터럴 타입, 열거형, 클래스까지 차례로 다룹니다.
JavaScript의 원시 값마다 대응하는 타입이 있습니다. 타입 이름은 모두 소문자로 씁니다. String처럼 대문자로 시작하는 것은 래퍼 객체 타입이므로 쓰지 않습니다.
| 타입 | 예 |
|---|---|
string | "hello", 템플릿 문자열 |
number | 42, 3.14, NaN (정수와 실수 구분 없음) |
boolean | true, false |
bigint | 123n |
symbol | Symbol("id") |
null · undefined | 값이 없음을 나타냄 |
그 밖에 알아 둘 특별한 타입이 있습니다. any는 타입 검사를 끄는 탈출구라 되도록 피합니다. unknown은 무엇이든 담을 수 있지만 쓰기 전에 타입을 확인해야 하는 안전한 버전입니다. void는 반환값이 없는 함수에, never는 절대 끝나지 않거나 일어날 수 없는 경우에 씁니다.
let id: bigint = 9007199254740993n;
let anything: unknown = JSON.parse('{"a":1}');
// anything.a; // 오류: unknown은 먼저 확인해야 합니다
if (typeof anything === "object" && anything !== null) {
console.log("객체입니다");
}
function fail(message: string): never {
throw new Error(message); // 정상적으로 반환하지 않습니다
}배열은 타입[] 또는 Array<타입>으로 씁니다. 바꾸면 안 되는 배열은 readonly를 붙입니다. 튜플은 길이와 각 위치의 타입이 정해진 배열입니다.
const tags: string[] = ["ts", "web"];
const ids: Array<number> = [1, 2, 3];
const frozen: readonly string[] = ["a", "b"];
// frozen.push("c"); // 오류: readonly 배열
const point: [number, number] = [10, 20];
const entry: [string, number] = ["age", 30];
const [key, value] = entry; // key: string, value: number
function minMax(values: number[]): [number, number] {
return [Math.min(...values), Math.max(...values)];
}객체의 모양은 인터페이스(interface)나 타입 별칭(type)으로 이름을 붙여 정의합니다. ?는 선택 속성, readonly는 읽기 전용 속성입니다.
interface User {
readonly id: number;
name: string;
email?: string; // 없어도 됩니다
}
interface Admin extends User { // 인터페이스 확장
permissions: string[];
}
type Point = { x: number; y: number };
type Point3D = Point & { z: number }; // 교차 타입으로 합치기
const admin: Admin = { id: 1, name: "Kim", permissions: ["write"] };
// admin.id = 2; // 오류: 읽기 전용TypeScript는 구조적 타입 시스템을 씁니다. 이름이 달라도 필요한 속성을 모두 갖추면 같은 타입으로 인정합니다. 두 방식의 차이는 다음과 같습니다.
| 항목 | interface | type |
|---|---|---|
| 객체 모양 정의 | 가능 | 가능 |
| 확장 방법 | extends | 교차 타입 & |
| 같은 이름으로 다시 선언해 합치기 | 가능 | 불가능 |
| 유니언 · 튜플 · 원시 타입 별칭 | 불가능 | 가능 |
객체 모양에는 interface, 유니언처럼 그 밖의 타입 조합에는 type을 쓰는 것이 흔한 관례입니다. 어느 쪽이든 팀 안에서 일관되게 쓰는 것이 더 중요합니다.
유니언 타입은 여러 타입 중 하나를 뜻하며 |로 잇습니다. 리터럴 타입과 함께 쓰면 허용되는 값을 정확히 제한할 수 있습니다.
type Id = number | string;
type Direction = "up" | "down" | "left" | "right";
function move(direction: Direction, steps = 1) {
console.log(`${direction} 방향으로 ${steps}칸`);
}
move("up");
// move("forward"); // 오류: 허용되지 않은 값
function printId(id: Id) {
if (typeof id === "string") {
console.log(id.toUpperCase()); // 여기서 id는 string
} else {
console.log(id.toFixed(0)); // 여기서 id는 number
}
}typeof로 확인한 뒤 타입이 좁혀지는 이 동작을 내로잉(narrowing)이라고 하며, 6장에서 자세히 다룹니다.
enum은 이름 붙은 상수 묶음입니다. 숫자 열거형과 문자열 열거형이 있으며, 디버깅할 때 값이 그대로 보이는 문자열 열거형이 다루기 쉽습니다. enum은 실제 JavaScript 객체를 만들어 내는 몇 안 되는 TypeScript 문법입니다. 그래서 타입만 지우는 실행 도구와 맞지 않을 수 있어, as const 객체와 유니언 타입 조합을 대신 쓰는 프로젝트도 많습니다.
enum Status {
Active = "ACTIVE",
Paused = "PAUSED",
}
const s: Status = Status.Active;
const Color = {
Red: "red",
Green: "green",
} as const;
type Color = (typeof Color)[keyof typeof Color]; // "red" 또는 "green"
const c: Color = Color.Red;클래스에는 속성과 접근 제한자(public, private, protected)를 타입과 함께 적습니다. 생성자 매개변수에 제한자를 붙이면 속성 선언과 대입을 한 번에 할 수 있습니다. implements는 클래스가 인터페이스를 만족하는지 검사합니다.
interface Shape {
area(): number;
}
class Rectangle implements Shape {
constructor(
private readonly width: number,
private readonly height: number,
) {}
area(): number {
return this.width * this.height;
}
}
class Counter {
#count = 0; // JavaScript 고유의 비공개 필드
increment(): number {
return ++this.#count;
}
}
const shapes: Shape[] = [new Rectangle(2, 3)];
console.log(shapes[0].area()); // 6private은 컴파일 시점에만 검사하고, #으로 시작하는 필드는 실행 시점에도 외부에서 접근할 수 없습니다.
string, number, boolean 등)로 쓰고, any 대신 unknown을 씁니다.T[], 고정 길이는 튜플 [A, B], 변경 금지는 readonly로 표현합니다.interface, 유니언 등 조합은 type으로 정의하는 것이 일반적입니다.as const 객체를 쓸 수도 있습니다.implements로 타입을 표현합니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.