본문으로 건너뛰기
김신건의 로그

[TypeScript] Type Aliases

· 수정 · 📖 약 2분 · 747자/단어 #typescript #types #type-alias
TypeScript Type Alias, TypeScript type, TS type alias, type declaration, TS 타입 별칭

정의

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).

관용 선택 규칙

  1. Object shape 이고 확장 필요 없음 -> type
  2. Object shape 이고 declaration merging 필요 -> interface
  3. Primitive alias, tuple, union, intersection, mapped, conditional -> type
  4. 라이브러리 사용자에게 확장 가능성 열어두기 -> interface
  5. 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 은 컴파일러가 그대로 유지. 성능 손실.

관련 위키

이 글의 용어 (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, …

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기