← 전체 글로 돌아가기

TypeScript

TypeScript 타입 가드로 초보자가 빠지는 함정들

null과 optional 처리를 놓쳐서 런타임 에러가 생기는 경우들과 해결책.

TypeScript를 배우기 시작한 사람들이 자주 마주치는 문제가 있다. 타입 체크를 했다고 생각했는데, 실행하면 "Cannot read property of null"이 튀어나온다.

null 체크 빼먹기

가장 흔한 실수다.

// ❌ 타입은 확인했지만 null은 안 했음
function getName(user: { name: string } | null) {
  // user가 { name: string } 또는 null
  return user.name; // 에러!
}

// ✅ 올바른 버전
function getName(user: { name: string } | null) {
  if (user === null) {
    return 'Unknown';
  }
  return user.name; // 안전
}

// 또는 optional chaining
return user?.name ?? 'Unknown';

optional 프로퍼티 다루기

interface User {
  id: string;
  name: string;
  email?: string; // 선택사항
}

function printEmail(user: User) {
  // ❌ email이 없을 수도 있음
  console.log(user.email.toLowerCase()); // 에러 가능

  // ✅ 체크 후 사용
  if (user.email) {
    console.log(user.email.toLowerCase());
  }
}

배열 요소 접근

// ❌ 배열이 비어있을 수 있음
function getFirst<T>(arr: T[]): T {
  return arr[0]; // undefined 반환 가능
}

// ✅ 타입으로 명시
function getFirst<T>(arr: T[]): T | undefined {
  return arr[0];
}

// ✅ 또는 런타임에 확인
function getFirstSafe<T>(arr: T[], defaultValue: T): T {
  return arr.length > 0 ? arr[0] : defaultValue;
}

strictNullChecks 설정

tsconfig.json에서:

{
  "compilerOptions": {
    "strictNullChecks": true,
    "noImplicitAny": true,
    "noImplicitThis": true
  }
}

이 옵션들을 켜야만 null/undefined 관련 에러를 빌드 타임에 잡을 수 있다.

객체 프로퍼티 접근

interface Config {
  api?: {
    timeout?: number;
  };
}

const config: Config = {};

// ❌ 각 단계에서 undefined일 수 있음
const timeout = config.api.timeout; // 에러!

// ✅ optional chaining
const timeout = config.api?.timeout; // number | undefined

// ✅ nullish coalescing
const timeout = config.api?.timeout ?? 3000; // number

함수 매개변수

// ❌ 잠깐, message가 undefined일 수도 있는데?
function log(message: string) {
  console.log(message.toUpperCase());
}

log(undefined); // 타입 에러!

// ✅ 명확하게
function log(message: string | undefined) {
  if (message) {
    console.log(message.toUpperCase());
  }
}

// ✅ 또는 기본값
function log(message: string = 'default') {
  console.log(message.toUpperCase());
}

불변성 체크

interface Point {
  readonly x: number;
  readonly y: number;
}

const point: Point = { x: 0, y: 0 };
// point.x = 5; // ❌ 에러! readonly이므로

타입 가드 함수

function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function printLength(value: unknown) {
  if (isString(value)) {
    console.log(value.length); // ✅ string으로 좁혀짐
  }
}

이 패턴은 복잡한 타입 확인이 필요할 때 매우 유용하다.

API 응답 다루기

interface ApiResponse {
  data?: {
    users?: Array<{
      id: string;
      name: string;
    }>;
  };
}

const response: ApiResponse = {};

// ❌ 너무 많은 가정
const firstUser = response.data.users[0]; // 에러!

// ✅ 단계별 확인
const users = response.data?.users;
const firstUser = users?.[0];
const name = firstUser?.name ?? 'Unknown';

빌드하면서 확인

npm run build

타입 에러가 있으면 빌드가 실패하도록 설정해두자. 런타임에서 발견하는 것보다 훨씬 낫다.

정리

TypeScript의 타입 가드는 "이 값이 정말 이 타입이 맞나?"를 묻는 과정이다. null이 섞여있으면 절대 "안 된다"고 생각하고, 항상 확인하는 습관이 중요하다.