[TypeScript] Type Aliases
정의
Type Alias 는 타입에 이름을 붙이는 TypeScript 문법 (type Name = ...). Interface 는 객체 shape 만 표현하지만, type alias 는 모든 타입 표현식 (primitive, union, intersection, tuple, mapped, conditional 등) 에 이름을 붙일 수 있습니다.
기본 사용
type UserId = number;
type UserName = string;
type Point = {
x: number;
y: number;
};
type Callback = (result: string) => void;
type Direction = "north" | "south" | "east" | "west";
언제 type, 언제 interface
두 문법이 겹치는 영역이 넓지만, type 만 할 수 있는 것 이 많습니다.
type 만 할 수 있는 것
1. Primitive alias:
type EmailAddress = string;
type Duration = number; // seconds
2. Union type:
type Status = "loading" | "success" | "error";
type Value = string | number | null;
3. Intersection type:
type Timestamped = {createdAt: Date; updatedAt: Date};
type User = {id: number; name: string};
type TimestampedUser = User & Timestamped;
4. Tuple:
type Point = [number, number];
type NamedPoint = [name: string, x: number, y: number];
5. Mapped type:
type ReadonlyUser = {readonly [K in keyof User]: User[K]};
6. Conditional type:
type Extract<T, U> = T extends U ? T : never;
7. Template literal type:
type Greeting = `Hello, ${string}`;
8. Utility type composition:
type PartialUser = Partial<User>;
type UserKeys = keyof User;
interface 만 할 수 있는 것
1. Declaration merging:
interface Window { myGlobal: string }
interface Window { another: number }
// = merged
2. Module augmentation (라이브러리 확장):
declare module "express" {
interface Request {
userId?: string;
}
}
3. class implements 에 잘 맞음 (type 도 되지만 관용은 interface).
관용 선택 규칙
- Object shape 이고 확장 필요 없음 -> type
- Object shape 이고 declaration merging 필요 -> interface
- Primitive alias, tuple, union, intersection, mapped, conditional -> type
- 라이브러리 사용자에게 확장 가능성 열어두기 -> interface
- API response type (외부 데이터) -> type (zod schema 와 짝)
일관성 있게 선택. 팀 프로젝트는 한 스타일로 통일 이 무엇보다 중요 (ESLint 로 강제 가능).
Type Alias 는 새 타입이 아님
type UserId = number;
type ProductId = number;
const uid: UserId = 1;
const pid: ProductId = uid; // OK! 둘 다 그냥 number
Type alias 는 별칭 이지 새 nominal type 이 아닙니다. 진짜 구분이 필요하면 branded type:
type Brand<T, B> = T & {__brand: B};
type UserId = Brand<number, "UserId">;
type ProductId = Brand<number, "ProductId">;
function createUserId(n: number): UserId { return n as UserId; }
function createProductId(n: number): ProductId { return n as ProductId; }
const uid = createUserId(1);
const pid: ProductId = uid; // 오류
__brand 는 실제 필드가 아님 (컴파일 시 삭제). 순수 타입 트릭.
Generic Type Alias
type Nullable<T> = T | null;
type Optional<T> = T | undefined;
type Maybe<T> = T | null | undefined;
type ApiResponse<T> = {
status: "ok" | "error";
data?: T;
error?: string;
};
type PromiseOrValue<T> = T | Promise<T>;
자세한 것은 Generics 참조.
Recursive Type Alias
type Json =
| string
| number
| boolean
| null
| Json[]
| {[key: string]: Json};
const value: Json = {
name: "kim",
age: 30,
hobbies: ["read", "hike"],
meta: {active: true},
};
Tree 구조도 재귀 표현:
type TreeNode<T> = {
value: T;
children: TreeNode<T>[];
};
Discriminated Union
type Shape =
| {kind: "circle"; radius: number}
| {kind: "square"; side: number}
| {kind: "rectangle"; width: number; height: number};
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.radius ** 2;
case "square": return s.side ** 2;
case "rectangle": return s.width * s.height;
}
}
kind 필드 (discriminant) 로 union 을 좁힘. TypeScript 가 각 case 에서 정확한 타입으로 narrow.
자세한 것은 Type Narrowing 참조.
Function Type
type Handler = (event: Event) => void;
type Predicate<T> = (item: T) => boolean;
type Comparator<T> = (a: T, b: T) => -1 | 0 | 1;
type Middleware = (req: Request, res: Response, next: () => void) => void;
Generic:
type Mapper<T, U> = (item: T) => U;
const toString: Mapper<number, string> = (n) => n.toString();
Type Alias 안에서 keyof, typeof
type User = {id: number; name: string; email: string};
type UserKeys = keyof User; // "id" | "name" | "email"
type UserValues = User[keyof User]; // number | string
type UserIdType = User["id"]; // number
// typeof 로 값에서 타입 추출
const CONFIG = {host: "localhost", port: 8080} as const;
type Config = typeof CONFIG; // {readonly host: "localhost", readonly port: 8080}
type ConfigKeys = keyof typeof CONFIG; // "host" | "port"
as const 는 리터럴 타입 유지에 매우 유용.
Mapped Type
Object 의 모든 필드를 변형:
type Readonly<T> = {readonly [K in keyof T]: T[K]};
type Partial<T> = {[K in keyof T]?: T[K]};
type Required<T> = {[K in keyof T]-?: T[K]};
type Nullable<T> = {[K in keyof T]: T[K] | null};
자세한 것은 Mapped Types 참조.
Conditional Type
type IsString<T> = T extends string ? true : false;
type A = IsString<"hi">; // true
type B = IsString<42>; // false
type ReturnType<F> = F extends (...args: any[]) => infer R ? R : never;
type X = ReturnType<() => number>; // number
자세한 것은 Conditional Types 참조.
Template Literal Type
type Greeting = `Hello, ${string}`;
type CssUnit = `${number}${"px" | "em" | "%"}`;
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
자세한 것은 Template Literal Types 참조.
Utility Type Composition
type User = {id: number; email: string; password: string; role: string};
type UserPublic = Omit<User, "password">;
type UserUpdate = Partial<Omit<User, "id">>;
type UserRole = Pick<User, "role">;
type UserKeys = keyof User;
자세한 것은 Utility Types 참조.
Type Alias 성능
같은 표현식을 여러 곳에서 반복하지 말고 alias 로 이름 붙이는 편이 컴파일 성능에 유리. 특히 mapped/conditional 조합은 캐시 히트가 중요.
// 반복 확장 (느림)
type A = {x: {a: number; b: string}; y: {a: number; b: string}; z: {a: number; b: string}};
// alias 로 캐시 (빠름)
type Inner = {a: number; b: string};
type A = {x: Inner; y: Inner; z: Inner};
함정
WARNING
Type alias 는 새 타입이 아님. Branded type 없이 type UserId = number 는 다른 number 와 호환.
CAUTION
Recursive type 은 컴파일 성능 영향. 재귀 깊이가 깊거나 조건 분기가 많으면 느려짐. 필요시 Depth 파라미터로 재귀 제한.
WARNING
as const 를 잊으면 리터럴이 넓어짐. const CONFIG = {port: 8080} 은 {port: number} 로 넓혀짐. Literal 유지하려면 as const.
IMPORTANT
Interface vs Type 을 팀에서 통일. 두 방식 섞이면 리팩토링 혼란. ESLint @typescript-eslint/consistent-type-definitions.
CAUTION
type X = {} & Y 같은 no-op intersection 은 컴파일러가 그대로 유지. 성능 손실.
관련 위키
- TypeScript - 상위 개요
- Interfaces - 대비 개념
- Generics
- Primitive Types
- Union / Intersection
- Type Narrowing - Discriminated union
- Mapped Types
- Conditional Types
- Template Literal Types
- Utility Types
- Enums - Literal union 대안
이 글의 용어 (11개)
- [TypeScript] Conditional Typestypescript
- 정의 Conditional Type 은 형태로 타입 레벨에서 조건 분기를 표현합니다. 키워드로 매칭된 타입을 추출하고, union 에 대해서는 자동 분배 (distributive…
- [TypeScript] Enumstypescript
- 정의 Enum 은 여러 관련 상수를 하나의 이름 아래 그룹화하는 TypeScript 기능입니다. Numeric enum, string enum, const enum, hetero…
- [TypeScript] Genericstypescript
- 정의 Generics 는 타입을 파라미터화하는 문법입니다. 함수, 클래스, 인터페이스, type alias 가 재사용 가능 하면서도 타입 안전 하도록 만듭니다. Java 의 ge…
- [TypeScript] Interfacestypescript
- 정의 Interface 는 객체의 shape (형태) 을 정의하는 TypeScript 문법입니다. 필드, 메서드, 인덱스 시그니처, 호출 시그니처를 선언하며, 로 상속하고 dec…
- [TypeScript] Mapped Typestypescript
- 정의 Mapped Type 은 기존 타입의 각 필드를 순회 ( ) 하며 새 타입을 생성하는 문법입니다. , modifier 를 붙이거나 제거 ( , ) 하고, clause 로 키…
- [TypeScript] Primitive Typestypescript
- 정의 Primitive Types 는 TypeScript 의 가장 기본 타입 계열입니다. JavaScript 의 7가지 primitive 값 + 특수 타입 (void, never…
- [TypeScript] Template Literal Typestypescript
- 정의 Template Literal Type 은 TypeScript 4.1 에서 도입된 문법으로, JavaScript template literal 처럼 백틱과 를 이용해 타입 …
- [TypeScript] Type Narrowing (Type Guards)typescript
- 정의 Type Narrowing 은 TypeScript 컴파일러가 제어 흐름 분석 (control flow analysis) 을 통해 특정 위치에서 값의 타입을 더 좁게 추론하는…
- [TypeScript] Union & Intersection Typestypescript
- 정의 - Union Type ( ): A 이거나 B (또는 둘 다) 인 값 - Intersection Type ( ): A 이면서 B 인 값 두 조합은 TypeScript 의 타…
- [TypeScript] Utility Typestypescript
- 정의 Utility Types 는 TypeScript 표준 라이브러리에 포함된 미리 정의된 generic 타입 들입니다. 기존 타입을 변형/구성하는 흔한 패턴을 제공하며, map…
- TypeScripttypescript
- 정의 TypeScript 는 Microsoft 가 2012년 발표한 JavaScript 의 상위 집합 프로그래밍 언어입니다. 정적 타입 시스템, 인터페이스, 제네릭, enum, …
💬 댓글