TypeScript null, undefined, optional 체이닝과 타입 가드 (12강)
TypeScript는 **null**과 **undefined**를 안전하게 처리할 수 있는 강력한 기능들을 제공합니다. 그 중에서 **optional 체이닝**과 **타입 가드**는 변수나 객체의 값을 안전하게 접근하고, 예기치 않은 오류를 방지하는 데 큰 도움이 됩니다. 이번 강의에서는 **null**과 **undefined** 처리, **optional 체이닝**, 그리고 **타입 가드**의 활용 방법을 실무 예제와 함께 자세히 설명하겠습니다.
TypeScript에서 null과 undefined 처리하기
TypeScript에서는 **null**과 **undefined**를 특별한 타입으로 구분하여 처리할 수 있습니다. 이 두 값은 **빈 값**을 의미하며, JavaScript에서 발생할 수 있는 오류를 방지하기 위해 TypeScript에서는 이를 다루는 데 신경을 씁니다.
null과 undefined의 차이
**null**과 **undefined**는 비슷한 개념이지만, 서로 다른 의미를 가집니다:
- null: 값이 **없음**을 명시적으로 나타냅니다. 변수가 의도적으로 값이 없음을 나타내기 위해 사용됩니다.
- undefined: 변수에 **값이 할당되지 않은 상태**를 나타냅니다. 초기화되지 않은 변수나 프로퍼티에서 기본적으로 반환되는 값입니다.
strictNullChecks 옵션
TypeScript에서는 **strictNullChecks** 옵션을 활성화하여 **null**과 **undefined**를 더 안전하게 처리할 수 있습니다. 이 옵션이 활성화되면, **null**이나 **undefined**가 허용되지 않는 곳에 이를 사용하면 컴파일러가 오류를 발생시킵니다. 즉, **null**이나 **undefined**를 명시적으로 처리해야만 합니다.
let value: string = null; // 컴파일 오류
let value2: string | null = null; // 컴파일 허용
이처럼 **`strictNullChecks`**가 활성화된 상태에서 **null**이나 **undefined**를 사용할 때는 명시적으로 타입을 지정하거나 안전한 처리가 필요합니다.
Optional 체이닝 (Optional Chaining) 이란?
**Optional 체이닝(optional chaining)**은 객체의 깊은 프로퍼티나 메서드에 접근할 때, 값이 **null** 또는 **undefined**일 경우 오류를 발생시키지 않고 **`undefined`**를 반환하는 기능입니다. 이 기능은 객체나 배열을 다룰 때, 값이 없을 가능성이 있는 경로를 안전하게 처리할 수 있게 해줍니다.
Optional 체이닝 사용법
Optional 체이닝은 **`?.`** 연산자를 사용하여 객체의 프로퍼티나 메서드에 안전하게 접근할 수 있도록 해줍니다. 만약 객체가 **null**이나 **undefined**이면, 오류가 발생하는 대신 **`undefined`**를 반환하게 됩니다.
let user = { profile: { name: "John", age: 30 } };
// Optional chaining을 사용한 안전한 접근
let userName = user?.profile?.name; // "John"
let userCity = user?.address?.city; // undefined
위 예제에서 **user.address**는 존재하지 않지만, optional 체이닝을 사용하여 **`undefined`**를 안전하게 반환할 수 있습니다. 이렇게 값이 **null**이나 **undefined**일 때 자동으로 처리할 수 있어 코드의 안정성을 크게 향상시킵니다.
Optional 체이닝과 함수 호출
Optional 체이닝은 **함수 호출**에도 사용할 수 있습니다. 함수가 **null** 또는 **undefined**인 경우, 함수 호출을 안전하게 건너뛰고 **`undefined`**를 반환합니다.
let user = { getName: () => "John" };
// Optional chaining을 사용한 안전한 함수 호출
let userName = user.getName?.(); // "John"
let userAge = user.getAge?.(); // undefined
위 예제에서 **`user.getAge`**가 정의되지 않아서 **`undefined`**가 반환됩니다. 이처럼 optional 체이닝을 사용하면 함수 호출 시 발생할 수 있는 예기치 않은 오류를 방지할 수 있습니다.
타입 가드(Type Guards)란?
**타입 가드(type guard)**는 TypeScript에서 **값의 타입을 좁혀주는** 기능으로, 변수나 객체의 타입을 보다 구체적으로 판별하고, 그에 따라 안전하게 처리할 수 있도록 돕는 역할을 합니다. 타입 가드를 사용하면 **조건문**을 통해 변수나 객체가 특정 타입인지를 확인하고, 이후 코드를 타입에 맞게 작성할 수 있습니다.
타입 가드의 종류
TypeScript에서는 여러 가지 방식으로 타입을 좁힐 수 있습니다. 대표적인 타입 가드 방식은 다음과 같습니다:
1. instanceof 연산자
**`instanceof`** 연산자를 사용하여 객체가 특정 클래스의 인스턴스인지 확인할 수 있습니다. 이를 통해 객체의 타입을 좁힐 수 있습니다.
class Dog {
bark() {
console.log("Woof!");
}
}
class Cat {
meow() {
console.log("Meow!");
}
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark(); // dog 타입으로 좁혀짐
} else {
animal.meow(); // cat 타입으로 좁혀짐
}
}
위 예제에서 **`instanceof`**를 사용하여 **`animal`** 객체가 **Dog**인지 **Cat**인지를 확인한 후, 각 타입에 맞는 메서드를 호출합니다. 이렇게 **`instanceof`** 연산자를 통해 객체의 타입을 좁힐 수 있습니다.
2. typeof 연산자
**`typeof`** 연산자는 기본 타입(예: 문자열, 숫자, 불리언 등)을 판별하는 데 사용할 수 있습니다. 이를 통해 **문자열**이나 **숫자** 타입인지 확인하고, 그에 맞는 처리를 할 수 있습니다.
function printLength(value: string | number) {
if (typeof value === "string") {
console.log(value.length); // string 타입으로 좁혀짐
} else {
console.log(value.toFixed(2)); // number 타입으로 좁혀짐
}
}
위 예제에서 **`typeof`**를 사용하여 **`value`**가 **`string`** 타입인지 **`number`** 타입인지를 확인하고, 그에 맞는 메서드를 안전하게 호출합니다.
3. in 연산자
**`in`** 연산자는 객체의 프로퍼티가 존재하는지 확인하는 데 사용됩니다. 이를 통해 객체 타입을 좁힐 수 있습니다.
function printProfile(person: { name: string } | { age: number }) {
if ("name" in person) {
console.log(person.name); // name 프로퍼티가 존재하면 person은 { name: string } 타입으로 좁혀짐
} else {
console.log(person.age); // age 프로퍼티가 존재하면 person은 { age: number } 타입으로 좁혀짐
}
}
null과 undefined 처리 및 타입 가드 활용
**null**과 **undefined**를 안전하게 처리하려면 **optional 체이닝**과 **타입 가드**를 활용하는 것이 매우 중요합니다. 이를 통해 **null**이나 **undefined**가 있을 수 있는 값에 대해 오류 없이 안전하게 접근하고, **타입 안전성**을 높일 수 있습니다.
마무리: 안전한 코드 작성하기
**optional 체이닝**과 **타입 가드**는 TypeScript에서 **null**과 **undefined**를 안전하게 처리하고, 객체나 함수의 프로퍼티에 접근할 때 발생할 수 있는 오류를 방지하는 데 중요한 역할을 합니다. 이 기능들을 활용하면 코드가 **더 안전하고 견고**해지며, 예기치 않은 런타임 오류를 미리 방지할 수 있습니다. **strictNullChecks** 옵션을 활성화하여 TypeScript의 타입 시스템을 적극 활용하고, 더 안전한 코드를 작성해 보세요.
다음 강의에서는 TypeScript에서 **제네릭(Generic)**을 사용하여 더욱 재사용 가능한 코드를 작성하는 방법을 배워보겠습니다.