! (Non-null assertion operator)
!연산자는 개발자가 "이 값은 확실히 null/undefined가 아니다"라고 TypeScript 컴파일러에게 알려주는 것입니다. 하지만 실제 런타임에서는 아무런 체크도 하지 않기 때문에, 확실히 보장될때만 사용해야 하며, 가능하다면 if 문으로 null 체크를 하거나 옵셔널 체이닝을 사용하는 것이 더 안전한 방법입니다.
1. Null 체크 우회
name은 string | null 이지만, 개발자가 이 시점에서 확실히 null이 아님을 알고 있을 때 사용하면 됩니다.
// 1. NULL 체크 우회
let name: string | null = "Ecount";
let nameLength = name!.length;
2. 초기화 체크 우회
클래스 필드 name이 생성자에서 직접 초기화되지 않고, 다른 메소드에서 초기화될 것임을 알려줍니다. !가 없으면 TypeScript는 name이 생성자에서 초기화되지 않았다고 경고를 줍니다.
class User {
name!: string;
constructor() {
this.initalize();
}
private initialize() {
this.name = "ecount";
}
}
! 연산자는 타입 체크를 우회하므로 신중하게 사용해야 합니다. 가능하면 타입 가드나 조건문으로 명시적 체크를 하는 것이 더 안전하며, 특히 외부 데이터를 다룰 때는 ! 사용을 피하고 명시적인 null 체크를 하는 것이 더 좋습니다.
// 위험한 방법
function funcA(user: User | null) {
const name = user!.name; // 런타임 에러 가능성!
}
// 안전한 방법
function funcB(user: User | null) {
if (user === null) {
return;
}
const name = user.name; // 타입 가드 사용
}
?. (Optional Chaining)
옵셔널 체이닝은 객체, 배열, 함수에 사용할 수 있습니다. 접근하는 주체의 프로퍼티가 null 또는 undefined일 수 있다면 if 문을 사용하지 않고 넘어가게 하는 방법입니다. 객체가 null 또는 undefined이면 undefined를 리턴하고, 그렇지 않은 경우 데이터 값을 리턴합니다. 보통의 경우 아래와 같은 코드에서 타입 에러가 발생하지만, && 연산자를 사용하면 문제를 해결할 수 있습니다.
// 1. &&
// 에러가 발생하는 코드
let user = {};
alert(user.address.phone); // TypeError
// 에러가 발생하지 않는 코드
let user = {};
alert(user && user.address && user.address.phone); // undefined
?.의 경우 앞의 대상이 undefined이거나 null일 때 평가를 중단하고 undefined를 반환합니다. 장점은 코드를 간소화할 수 있고, 안전하게 객체에 접근이 가능합니다. 단점은 실제로는 문제가 있는 코드인데 에러가 발생하지 않아 디버깅이 어려울 수 있고, 타입 안정성이 저하됩니다.
let user = {};
alert(user?.address?.street); // undefined
?? (Nullish Coalescing)
널 병합 연산자는 왼쪽 피연산자가 null이거나 undefined일때만 오른쪽 피연산자를 반환하는 연산자입니다. 아래 코드에 따르면, comparer가 제공되지 않으면 기존에 구현해둔 DefaultGenericComparer를 사용하기 위해 널 병합 연산자를 사용하였습니다.
public constructor(comparer: IEqualityComparer<TValue> | null = null) {
this._count = 0;
this._head = null;
this._tail = null;
this._comparer = comparer ?? DefaultGenericComparer;
}