TypeScript
TypeScript에서 optional과 null을 구분해서 타입 에러 없애기
TypeScript의 타입 에러는 복잡해 보이지만, 대부분 optional(?)과 null/undefined를 헷갈렸거나 타입 가드를 빠뜨린 경우다.
혼자 TypeScript 프로젝트를 진행하다 보면 타입 에러가 쌓인다. 특히 "Cannot read property of undefined" 같은 런타임 에러를 방지하려고 타입 체크를 더 엄격하게 하려 할수록 더 많은 에러가 나타난다.
Optional과 Nullable 구분하기
TypeScript에서 가장 자주 헷갈리는 부분이다.
// Optional: 속성이 있을 수도 없을 수도 있음
type User = {
name: string;
nickname?: string; // undefined일 수 있음
};
// Nullable: 속성은 있지만 null/undefined 값을 가질 수 있음
type User2 = {
name: string;
nickname: string | null; // null일 수 있음
};
두 경우 모두 user.nickname에 접근하려면 체크가 필요하지만, 의미가 다르다. Optional은 속성 자체가 없을 수 있고, Nullable은 속성은 있지만 값이 null일 수 있다는 뜻이다.
타입 가드로 에러 방지하기
값을 사용하기 전에 항상 타입을 확인한다.
// 나쁜 예: 타입 체크 없이 사용
function greet(user: User) {
console.log(user.nickname.toUpperCase()); // nickname이 undefined일 수 있음
}
// 좋은 예: 타입 가드
function greet(user: User) {
if (user.nickname) {
console.log(user.nickname.toUpperCase());
}
}
// 더 명시적인 예
function greet(user: User) {
if (user.nickname && user.nickname !== null) {
console.log(user.nickname.toUpperCase());
}
}
타입 가드 함수 만들기
자주 나타나는 패턴은 타입 가드 함수로 따로 빼면 코드가 깔끔해진다.
// 배열에서 null이나 undefined를 제거
function isDefined<T>(value: T | undefined | null): value is T {
return value !== undefined && value !== null;
}
const items = [1, null, 2, undefined, 3];
const validItems = items.filter(isDefined); // [1, 2, 3]
빌드 에러 메시지 읽기
타입 에러 메시지는 길지만 실제로는 중요한 정보가 담겨있다.
Type '{ name: string; }' is not assignable to type '{ name: string; email: string; }'.
Property 'email' is missing in type '{ name: string; }'
이 메시지는 "email 속성이 필수인데 전달한 객체에 없다"는 뜻이다. 전달하는 객체에 email을 추가하거나, 타입을 변경해야 한다.
제네릭과 extends 활용
복잡한 타입을 다룰 때는 제네릭으로 보다 정확한 타입을 표현할 수 있다.
// 제네릭을 사용하지 않은 경우
function process(data: any): any {
return data;
}
// 제네릭으로 입출력 타입을 명시
function process<T>(data: T): T {
return data;
}
// API 응답 처리
interface ApiResponse<T> {
status: number;
data: T;
}
function fetchUser(id: number): Promise<ApiResponse<User>> {
// 구현
}
Strict 모드 단계적으로 활성화
프로젝트 시작부터 엄격한 설정을 할 수 없다면 점진적으로 활성화한다.
// tsconfig.json
{
"compilerOptions": {
"strict": false,
"strictNullChecks": true, // null/undefined 체크만 먼저
"noImplicitAny": true // any 타입 명시 요구
}
}
테스트와 타입 확인
타입이 정확한지 확인하려면 테스트를 작성한다.
test('User nickname should be optional', () => {
const user: User = { name: 'John' }; // nickname 없음
const user2: User = { name: 'Jane', nickname: 'j' }; // nickname 있음
// 둘 다 유효한 User
expect(user).toBeDefined();
expect(user2).toBeDefined();
});
체크리스트
- 타입 정의에서 optional(?)과 nullable (| null)을 구분한다.
- 값을 사용하기 전에 항상 타입 가드로 확인한다.
- 자주 쓰는 가드 패턴은 함수로 따로 만든다.
- 타입 에러 메시지를 끝까지 읽고 정확히 이해한다.
- 제네릭으로 타입을 더 정확하게 표현한다.
- 점진적으로 strict 설정을 활성화한다.
TypeScript 타입 에러는 번거로워 보이지만, 실제로는 런타임 에러를 사전에 방지하는 소중한 신호다.