TypeScript 4.6 strictNullChecks와 씨름한 기록

배경

2년 전 시작한 프로젝트의 TypeScript 설정을 강화하기로 했다. strictNullChecks를 켜는 순간 500개가 넘는 에러가 터졌다. 한 번에 해결할 수 없어서 파일별로 점진 적용하는 방식을 선택했다.

자주 마주친 패턴

1. API 응답 타입

// Before
interface User {
  id: string;
  name: string;
  email: string;
}

// After - 실제로는 null이 올 수 있었음
interface User {
  id: string;
  name: string;
  email: string | null;
}

백엔드에서 email이 null로 내려오는 경우가 있었는데, 타입에는 반영되지 않아 런타임 에러가 종종 발생했다.

2. Optional Chaining 남용

// Bad - 무분별한 사용
const userName = user?.profile?.name ?? 'Anonymous';

// Good - null 체크 명시
if (!user || !user.profile) {
  return 'Anonymous';
}
const userName = user.profile.name;

?.를 남발하면 실제 버그가 숨겨진다. 어디서 null이 들어올 수 있는지 명확히 하는 게 중요했다.

3. DOM 조작

// Before
const button = document.getElementById('submit');
button.addEventListener('click', handleClick); // 에러

// After
const button = document.getElementById('submit');
if (button) {
  button.addEventListener('click', handleClick);
}

getElementByIdHTMLElement | null을 반환한다. 이 부분을 놓쳐서 수정한 곳이 많았다.

점진 적용 전략

  1. //@ts-expect-error로 에러 마킹
  2. 주요 도메인 모델부터 타입 정확하게 수정
  3. 유틸 함수에 타입 가드 추가
  4. 매주 20~30개씩 해결

결과

2개월 걸려서 모든 파일에 적용 완료했다. 런타임 에러가 30% 정도 줄었고, 타입 추론이 정확해져서 자동완성도 개선됐다. 초반에 귀찮았지만 충분히 가치 있는 작업이었다.