본문 바로가기

TypeScript

타입 단언과 할당 단언

단언 : 주저하지 아니하고 딱 잘라 말함

타입 단언


타입 단언은 타입스크립트의 중요한 개념으로 어렵지 않게 사용할 수 있지만 잘못 사용할 경우 any 타입처럼 타입스크립트를 사용하는 이유가 없어질 수도 있기 때문에 신중하게 사용해야 합니다.

상단에 명시한 단언의 뜻대로 타입 단언이란 개발자가 타입스크립트에게 주저하지 않고 딱 잘라 말하는 것을 의미합니다. 무엇을 타입스크립트에게 단언하는지 아래 예시 코드와 함께 살펴보도록 하겠습니다.

예시 1
const el = document.querySelector("body");
el.textContent = "hello"; // 타입 에러 발생

위의 코드는 el(body 요소)에 문자 데이터를 할당하고 있습니다.

해당 예시 코드의 경우 위의 타입 에러 메시지를 확인할 수 있습니다. 메시지를 보면 상수 el의 타입은 union 타입으로 HTML 요소 이거나 null 타입으로 명시되어 있습니다.

해당 타입 에러가 발생하는 이유는 querySelector의 경우 선택자로 전달받은 요소를 찾지 못하면 null을 반환하기 때문에 위와 같은 타입을 명시받습니다. 또한 개발자의 경우 HTML 문서에 body 태그가 있음을 명확하게 알고 있지만 타입스크립트의 경우 스크립트 문서만 확인하고 추론을 하기 때문에 body 태그가 있는지 없는지는 알 수 없습니다.

앞서 언급한 대로 타입스크립트는 body 태그가 있는지 없는지 모르지만, 개발자는 명확하게 알고 있기 때문에 타입스크립트에게 아래와 같이 단언할 수 있습니다.

const el = document.querySelector("body") as HTMLBodyElement;
el.textContent = "hello";

단언 키워드인 as를 사용하여 HTMLBodyElement, 즉 HTML의 요소 타입으로 지정해줌으로 querySelector로 body 태그를 찾게 되면 HTMLBodyElement가 될 것이니 null이 아님을 타입 단언을 해줄 수 있습니다.

이렇게 타입 단언을 해주면 타입스크립트 입장에서는 개발자가 확신이 있다 판단하여 정상적으로 textContent를 사용하여 문자를 할당할 수 있게 됩니다.

하지만 예시 1의 경우 선택자로 HTML 문서에 당연히 있는 body 태그를 전달했기 때문에 타입 단언에 문제 될 것이 없지만 개별적으로 지정한 클래스 선택자나 아이디 선택자의 경우 해당 선택자에 존재하지 않다면 잘못된 타입 단언이기 때문에 아래와 같이 타입 단언 없이 코드를 수정해 줍니다.

const el = document.querySelector(".title");
if (el) {
  el.textContent = "hello";
}

위와 같이 조건문을 작성해주게 되면 querySelector의 값이 null이 아닐 경우에 textContent 속성을 사용하여 값을 할당할 수 있기 때문에 타입스크립트와 상관없이 자바스크립트 코드를 통해 문제를 해결할 수 있습니다.

이렇게 조건문을 사용하여 변수의 내용이 확정적일 때만 코드가 동작할 수 있도록 만들어주는 방식을 타입 가드라고 합니다.



예시 2
function getNumber(x: number | null | undefined) {
  return Number(x.toFixed(2)); // 타입 에러 발생
}

getNumber(3.1415926535);
getNumber(null);

함수 getNumber는 매개 변수 x의 타입이 위와 같은 union 타입으로 명시되어 있으며, 매개 변수 x에 toFixed 메서드를 사용하여 값을 반환하고 있습니다.

이때 toFixed 메서드의 경우 숫자 타입 데이터에 사용이 가능하기 때문에 null과 undefined 타입의 데이터가 매개 변수로 들어오면 사용이 불가능하기 때문에 아래의 타입 에러 메시지를 확인할 수 있습니다.

function getNumber(x: number | null | undefined) {
  return Number((x as number).toFixed(2));
}

이때 매개 변수 x에 number 타입이라고 타입 단언을 해주게 되면 타입 에러 메시지는 사라지게 되지만 함수의 호출 부분에서 인수로 null을 전달하고 있기 때문에 브라우저에서 타입 에러 메시지를 확인할 수 있게 됩니다.

이렇게 타입스크립트에 딱 잘라 단언을 했는데 잘못된 타입의 데이터가 인수로 들어왔으니 이는 잘못된 단언에 해당합니다. 이 경우 코드 상에서 문제를 발견할 수 없기 때문에 런타임에서 해당 오류를 확인해야 하며 앞서 언급한 대로 타입스크립트의 사용 이유가 사라지게 됩니다.

이러한 이유로 아래와 같이 타입 단언을 하지 않고 코드를 수정해 줍니다.

function getNumber(x: number | null | undefined) {
  if (x) {
    return Number(x.toFixed(2));
  }
}




예시 3
function getValue(x: string | number, isNumber: boolean) {
  if (isNumber) {
    return Number(x.toFixed(2)); // 타입 에러 발생
  }
  return x.toUpperCase(); // 타입 에러 발생
}

getValue("hello", false);
getValue(3.141592, true);

함수 getValue는 위와 같이 매개 변수 x에 union 타입을 명시해주고 있고, isNumber에 boolean 타입을 명시해주고 있습니다.

함수 getValue의 로직의 경우 isNumber가 true일 경우 매개 변수 x에 toFixed 메서드를 사용하여 반환하고 있으며, false일 경우 toUppercase 메서드를 사용하여 반환하고 있습니다.

문제는 위의 코드가 정상적인 코드임에도 불구하고 타입스크립트에서는 타입 에러가 발생하게 됩니다. 이는 매개 변수 x가 number 타입 또는 string 타입이라고 명시되어 있는데 toFixed 메서드와 toUppercase 메서드를 모두 사용할 수 없기 때문입니다.

function getValue(x: string | number, isNumber: boolean) {
  if (isNumber) {
    return Number((x as number).toFixed(2));
  }
  return (x as string).toUpperCase();
}

이때도 매개 변수로 들어오는 값의 타입이 개발자 입장에서 명확하다면 위와 같이 타입 단언을 해줄 수 있습니다.

Non-null 단언 연산자 - !

Non-null 단언연산자 !를 사용하면 null이나 undefined가 아니라고 단언해줄 수 있습니다.

const el = document.querySelector("body");
el!.textContent = "hello";

예시 1번에서 타입스크립트는 body요소가 있는지 없는지 알지 못하기 때문에 상수 el의 타입을 HTMLBodyElement 또는 null이라고 타입 추론을 하게 되는데 이때 타입 에러가 발생하는 부분 뒤에 !를 사용해주게 되면 el은 절대 null이 아니라고 타입스크립트에게 타입 단언을 해줄 수 있게 됩니다.

function getNumber(x: number | null | undefined) {
  return Number(x!.toFixed(2));
}

예시 2번도 동일한 방법으로 타입 에러가 발생하는 부분 뒤에 Non-null 단언연산자를 사용하여 매개 변수가 절대 null이나 undefined가 아니라고 타입 단언을 해줄 수 있습니다.



할당 단언


let num: number;
console.log(num); // 타입 에러 발생

변수 num에 별도의 값을 할당하지 않고 number 타입을 명시해주고 콘솔로 출력해주려고 하고 있으며 개발자는 undefined를 콘솔로 출력하고 싶은 상황입니다. 이렇게 타입만 명시해주고 값을 할당해주지 않을 경우 undefined가 할당되기 때문에 사용이 불가능하여 타입 에러가 발생하게 됩니다.

타입 에러 메시지를 확인해보면 값이 할당되기 전에 사용하고 있기 때문에 타입 에러가 발생함을 알 수 있습니다.

let num!: number;
console.log(num);

이때 타입 선언 전, 변수명 뒤에 !를 사용하면 할당 단언이 되며 원하는 대로 undefined가 콘솔로 출력되었으며 타입 에러도 사라지게 됩니다.

이는 타입스크립트에 해당 변수(num)에 할당을 했다고 단언했기 때문입니다. 어떻게 보면 할당하지 않고 할당을 했다고 단언을 했기 때문에 거짓말하는 것이 되는데, 코드 작동을 위한 선의의 거짓말 정도로 접근해주면 좋을 것 같습니다.

'TypeScript' 카테고리의 다른 글

인터페이스 (Interface)  (2) 2023.01.02
타입 가드(Guard)  (0) 2023.01.02
타입 추론 (Inference)  (2) 2023.01.01
TypeScript 타입 종류  (0) 2023.01.01
TypeScript 기본 정리  (0) 2023.01.01