TypeScript Strict Mode 마이그레이션: 점진적 타입 안전성 확보
TypeScript Strict Mode로의 마이그레이션 전략과 점진적 적용 방법을 설명합니다.
TypeScript Strict Mode 마이그레이션
Introduction
TypeScript Strict Mode는 컴파일러의 타입 검사를 강화하여 런타임 에러를 사전에 방지할 수 있게 해주는 기능입니다. 기존 JavaScript 코드베이스를 TypeScript로 마이그레이션할 때 Strict Mode를 한 번에 활성화하는 것은 매우 어려울 수 있습니다. 이 글에서는 점진적인 마이그레이션 전략과 실제 적용 방법을 다루겠습니다.
Environment
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": false,
"noImplicitAny": false,
"strictNullChecks": false,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}# TypeScript 버전 확인
npx tsc --version
# 5.3.3Problem
Strict Mode 비활성화 상태의 문제점:
// noImplicitAny: false 상태
function processUser(user) {
return user.name.toUpperCase(); // user의 타입이 any
// 컴파일 에러 없음
// 런타임: user가 null이면 TypeError 발생!
}
// strictNullChecks: false 상태
function getUser(id: number) {
const users = [{ id: 1, name: 'Joel' }];
return users.find(u => u.id === id);
// 반환 타입: { id: number; name: string } | undefined
// strictNullChecks가 꺼지면 undefined 체크 불필요로 간주
}
const user = getUser(999);
console.log(user.name); // 컴파일 에러 없음, 런타임: Cannot read property 'name' of undefined# 에러 로그
$ npx tsc --noEmit
# TypeScript 컴파일 에러 없음 (strict: false)
# 런타임에 에러 발생 가능Analysis
Strict Mode 관련 에러를 분석했습니다:
# Strict Mode 활성화 시 에러 수 확인
npx tsc --strict --noEmit 2>&1 | wc -l
# 2847개 에러!
# 에러 유형 분석
npx tsc --strict --noEmit 2>&1 | grep "TS" | sort | uniq -c | sort -rn
# TS7006: Parameter 'x' implicitly has an 'any' type (1234)
# TS2531: Object is possibly 'null' (567)
# TS2345: Argument of type 'X' is not assignable (345)
# TS18047: 'x' is possibly 'null' (234)// 각 strict 옵션별 에러 분석
// noImplicitAny: 1234개
// strictNullChecks: 801개
// strictFunctionTypes: 234개
// strictBindCallApply: 123개
// noImplicitThis: 56개
// alwaysStrict: 0개Solution
1단계: 점진적 옵션 활성화
// tsconfig.json - 1단계
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": false,
// 점진적 활성화 옵션
"noImplicitAny": true,
"strictNullChecks": false,
"strictFunctionTypes": false,
"strictBindCallApply": false,
"noImplicitThis": false,
"alwaysStrict": false,
"useUnknownInCatchVariables": false,
"exactOptionalPropertyTypes": false,
"noPropertyAccessFromIndexSignature": false
}
}2단계: noImplicitAny 해결
// before: 암시적 any 타입
function processUser(user) {
return user.name.toUpperCase();
}
// after: 명시적 타입 정의
interface User {
id: number;
name: string;
email?: string;
}
function processUser(user: User): string {
return user.name.toUpperCase();
}
// unknown 타입 활용
function parseJSON(input: unknown): User {
if (typeof input !== 'object' || input === null) {
throw new Error('Invalid input');
}
const obj = input as Record;
if (typeof obj.id !== 'number' || typeof obj.name !== 'string') {
throw new Error('Invalid user object');
}
return {
id: obj.id,
name: obj.name,
email: typeof obj.email === 'string' ? obj.email : undefined
};
} 3단계: strictNullChecks 해결
// before: null 체크 누락
function getUser(id: number) {
const users: User[] = [{ id: 1, name: 'Joel' }];
return users.find(u => u.id === id);
}
const user = getUser(999);
console.log(user.name); // TS2531: Object is possibly 'null'
// after: 안전한 null 처리
function getUser(id: number): User | undefined {
const users: User[] = [{ id: 1, name: 'Joel' }];
return users.find(u => u.id === id);
}
const user = getUser(999);
if (user) {
console.log(user.name); // OK
} else {
console.log('User not found');
}
// Optional chaining 활용
const userName = user?.name ?? 'Unknown';
// Non-null assertion (신중하게 사용)
const guaranteedUser = getUser(1)!; // null 아님을 단언4단계: 전체 Strict Mode 활성화
// tsconfig.json - 최종 상태
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}// 전체 Strict Mode 적용 후
interface ApiResponse {
data: T;
status: number;
message?: string;
}
async function fetchData(url: string): Promise> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result: ApiResponse = await response.json();
return result;
}
// 타입 안전성 보장
const result = await fetchData('/api/user/1');
if (result.status === 200) {
console.log(result.data.name);
} else {
console.error(result.message ?? 'Unknown error');
} Lessons Learned
- 점진적 접근: 한 번에 Strict Mode를 활성화하지 말고, 옵션별로 점진적으로 적용해야 합니다
- noImplicitAny 우선: 가장 많은 에러를 발생시키는
noImplicitAny부터 해결하는 것이 효과적입니다 - 类型守卫 활용:
unknown타입과 타입 가드를 활용하여 안전한 타입 단언을 수행해야 합니다 - CI/CD 통합: TypeScript 컴파일 에러를 CI/CD 파이프라인에서 검사하여 새 코드에서 회귀를 방지해야 합니다
- 팀 교육: Strict Mode 마이그레이션은 팀원들의 TypeScript 이해도 향상과 함께 진행하는 것이 효과적입니다
이 블로그는 외부 스폰서십, 제휴 마케팅 또는 광고 수익을 받지 않습니다.