Post

TypeScript 데이터 타입 (3) | Interface·Utility·Enum 활용

Interface와 Utility Types, Enum을 활용해 타입을 확장하고 재사용하는 실무 패턴을 살펴봅니다.

TypeScript 데이터 타입 (3) | Interface·Utility·Enum 활용

1. 인터페이스 (Interface)

  • 인터페이스는 객체의 형태(구조)를 정의한다.
  • extends, readonly, ?, [key: string] 등을 통해 유연하고 확장 가능하다.
  • type과 달리 병합 (Declaration Merging) 이 가능하다.

1-1. 기본 구조

  • 객체의 속성과 타입 구조를 미리 정의해 일관된 형태의 데이터를 유지하게 한다.
1
2
3
4
5
6
7
8
9
interface User {
  name: string;
  age: number;
}

const user: User = {
  name: "jerry",
  age: 30,
};

1-2 선택적 속성과 읽기 전용 속성

  • interface에서도 객체 리터럴 타입과 동일하게 ?(옵션), readonly(읽기 전용) 을 사용할 수 있다.
  • 재사용성과 확장성 면에서 type보다 유리하다.
1
2
3
4
5
6
7
8
9
interface User {
  name: string;
  age?: number; // 선택적 속성
  readonly id: number; // 읽기 전용 속성
}

const person: User = { id: 1, name: "poby" };
person.name = "jerry";
person.id = 2; // ❌ 에러 - 읽기 전용은 값 변경 안됨

1-3. 인터페이스 확장 (extends)

  • extends를 사용하면 기존 인터페이스를 상속받하여 속성 추가할 수 있다.
  • 중복 선언 없이 구조 재사용이 가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Person {
  name: string;
  age: number;
}

interface Developer extends Person {
  skills: string[];
}

const dev: Developer = {
  name: "jerry",
  age: 30,
  skills: ["React", "Figma"],
};

1-4. 병합 (Declaration Merging)

  • 같은 이름의 인터페이스를 여러 번 선언하면 자동으로 병합된다.
  • type에서는 불가능한 인터페이스 고유기능이다.
1
2
3
4
5
6
7
8
9
10
11
interface Product {
  name: string;
}
interface Product {
  price: number;
}

const item: Product = {
  name: "Keyboard",
  price: 49000,
};

1-5. 인덱스 시그니처 (Index Signature)

  • 키 이름이 미리 정해지지 않은 객체 구조를 정의할 때 사용한다.
  • 모양만 같으면 같은 타입으로 인식한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// type 별칭으로 정의(interface와 같은 의미)
type StringDictionary = {
  [key: string]: string;
};

// interface로 정의(확장성과 병합)
interface StringDictionary {
  [key: string]: string; 
}

const colors: StringDictionary = {
  red: "#FF0000",
  blue: "#0000FF",
};

1-6. 정리

키워드기능예시
?선택 속성age?: number
readonly수정 불가readonly id: number
extends확장interface B extends A
병합자동 합침interface A {} + A {}
[key: string]동적 키`{ [k: string]: string

2. 유틸리티 타입 (Utility Types)

  • 타입을 만드는 함수형 도구이다.
  • 제네릭(Generic)과 조건부 타입을 활용해 타입을 재가공한다
  • 실무에서 데이터 구조를 변형하거나 일부 속성만 제어할 때 자주 사용된다.

2-1. Partial

  • 특정 타입의 모든 속성을 선택적 (옵션) 으로 바꾼다.
  • 일부 속성만 작성해도 오류가 나지 않는다.
1
2
3
4
5
6
7
8
9
10
interface Post {
  title: string;
  tags: string[];
  content: string;
  thumnailURL? : string
}

const draft: Partial<Post> = { // 일부만 있어도 OK
  title: `jerry's blog`
}

2-2. Required

  • 특정 객체 타입의 모든 속성을 필수 (required) 로 만든다.
  • 하나라도 빠지면 오류 발생한다.
1
2
3
4
5
6
7
interface User { 
  name: string; 
  age?: number; }

const u2: Required<User> = { 
  name: "jerry" }; // ❌ 오류! age가 빠져있음

2-3. Pick

  • 필요한 속성만 골라서 새로운 타입을 만든다.
1
2
3
4
5
6
7
interface User { 
  name: string;
  age: number; 
  email: string; }

type BasicInfo = Pick<User, "name" | "age">;
const u3: BasicInfo = { name: "jerry", age: 3 };

2-4. Omit<T,K>

  • 특정 속성만 제외 (Omit) 해서 새 타입을 만든다.
  • Pick 의 반대 개념이다.
1
2
3
4
5
6
7
8
type ProductBasic = Omit<Product, "description">;

const p2: ProductBasic = {
  id: 2,
  name: "딸기우유",
  price: 1800,
  // description: "맛있어요" 은 제외됨
};

2-5. Readonly

  • 모든 속성을 읽기 전용(Readonly) 으로 만들어 수정이 불가능하도록 한다.
1
2
3
4
5
6
7
const user: Readonly<User> = {
  name: "Poby",
  age: 5,
  email: "poby@gmail.com",
};

user.age = 6; // ❌ 에러!

2-6. Record<K,T>

  • keyvalue의 타입을 동시에 정의할 수 있는 구조
  • 객체를 사전처럼 다룰 때 유용하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
// Key는 string, Value는 boolean
const permission: Record<string, boolean> = {
  admin: true,
  editor: false,
};

// Key를 특정 값으로 제한할 수도 있음
type Role = "admin" | "user" | "guest";
const roleAccess: Record<Role, number> = {
  admin: 1,
  user: 2,
  guest: 3,
};

2-7. 한눈에 비교하기

유틸리티설명예시
Partial속성 선택적Partial<User>
Required속성 필수Required<User>
Pick선택 사용Pick<User, "name">
Omit제외 사용Omit<User, "pw">
Readonly읽기 전용Readonly<User>
Recordkey-valueRecord<string, num>

3. enum (열거형)

  • 의미 있는 상수 집합을 정의하는 타입.
  • 고정된 선택지를 만들어 코드의 가독성과 안정성을 높인다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 기본형
enum Direction {
  Up,
  Down,
  Left,
  Right,
}

let move: Direction = Direction.Up;
console.log(move); // 0

// 문자열 (직접 값 지정)
enum Role {
  Admin = "ADMIN",
  User = "USER",
  Guest = "GUEST",
}

const userRole: Role = Role.Admin;
console.log(userRole); // "ADMIN"

3-1. as const + type (요즘 방식)

  • enum 대신 가벼운 상수 객체를 만드는 최신 패턴이다.
1
2
3
4
5
6
7
8
9
10
11
12
const Day = {
  Mon: "Mon",
  Tue: "Tue",
  Wed: "Wed",
  Thu: "Thu",
  Fri: "Fri",
} as const;

let today = Day.Mon; // ✔️ 가능
today = Day.Fri;     // ✔️ 가능
today = "Sunday";    // ❌ 에러! 

js vs ts 이미지


💡 학습정리

인덱스 시그니처 ➡ 키 이름이 정해지지 않은 객체 구조를 표현할 때 사용
interface ➡ 확장(extends), type ➡ 교차(&) 로 확장 방식이 다름
일반적으로 type은 데이터 구조 정의용, interface는 컴포넌트 propsAPI 응답 구조에 적합
Partial: 선택적 / Required: 필수 / Pick: 선택 / Omit: 제외 / Readonly: 수정 불가 / Record: 키-값 정의
enum: 정해진 값만 사용할 수 있는 상수 집합
as const: enum처럼 가볍게 쓸 수 있는 읽기 전용 상수 객체

This post is licensed under CC BY 4.0 by the author.