← 전체 글로 돌아가기

TypeScript

TypeScript Record 타입 제대로 사용하기

Record 타입을 모든 키를 필수로 만들지 않으면 런타임 에러가 난다. optional 처리를 정확히 해야 한다.

API 응답에서 사용자 ID별 권한을 매핑할 때 Record를 썼는데 배포 후 undefined 에러가 났다. 모든 사용자 ID가 응답에 포함된다고 가정했기 때문이다.

Record 타입의 기본

Record 타입은 특정 키와 값의 타입을 정의한다:

type Permissions = Record<string, boolean>;

// 이렇게 쓸 수 있다
const userPerms: Permissions = {
  user1: true,
  user2: false
};

하지만 이렇게 하면 모든 키가 있다고 가정한다. 없는 키로 접근하면 undefined가 나온다.

Optional 처리가 필요한 경우

데이터베이스나 API에서 온 데이터는 완전하지 않을 수 있다:

type UserPermissions = Record<string, boolean | undefined>;

const perms: UserPermissions = {};
console.log(perms.user1); // undefined (안전함)

아니면 Partial을 사용하면 모든 값이 optional이 된다:

type UserPermissions = Partial<Record<string, boolean>>;

const perms: UserPermissions = {};
console.log(perms.user1); // undefined (타입 안전)

타입 좁히기

존재하지 않는 키에 접근하는 걸 방지하려면:

function checkPermission(userId: string, perms: Record<string, boolean>): boolean {
  if (!(userId in perms)) {
    return false; // 없으면 false
  }
  return perms[userId];
}

또는 optional chaining과 nullish coalescing:

const hasPermission = perms[userId] ?? false;

컴파일 시 확인

npx tsc --noEmit

TypeScript 컴파일 단계에서 타입 에러를 모두 찾아야 한다. 런타임에 undefined로 터지는 건 방지할 수 있다.

실제 API 응답 다루기

API에서 온 데이터는 항상 불완전할 수 있다고 가정하자:

interface ApiResponse {
  users?: Record<string, { name: string; active: boolean }>;
}

function processUsers(response: ApiResponse) {
  const users = response.users ?? {};
  // users는 빈 객체일 수도 있고 몇몇 키만 있을 수도 있다
}

타입 좁히기의 중요성

런타임에 존재하지 않는 키에 접근하는 걸 피하는 게 핵심이다. TypeScript가 컴파일 단계에서 잡을 수 없는 부분들도 많으니, 런타임에 안전하게 처리하는 습관을 들이자.

작은 타입 체크가 배포 후 undefined 관련 버그를 많이 줄인다.