Backend/TypeScript

non-null 단언 연산자(!), null 병합 연산자(??) 그리고 옵셔널 체이닝(.?)

kimdozzi 2024. 12. 22. 14:29

! (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;
  }