쉽게 시작하는 타입스크립트: 6 - 타입단언

Bumgu·2024년 2월 1일
0

타입단언 (type assertion)은 타입스크립트의 타입 추론 (type inference)에 기대지 않고 개발자가 직접 타입을 명시하여 해당 타입으로 강제하는 것을 의미합니다.


변수를 선언하고 문자열을 할당했습니다. 타입스크립트의 타입추론덕분에 선언하는 시점의 초깃값으로 타입이 결정됩니다. 문자열을 할당했기때문에 string타입이됩니다.

여기에 as를 사용해 타입단언을 적용해보겠습니다.

as키워드를 붙이면 타입스크립트가 컴파일할 때 해당 코드의 타입 검사는 수행하지 않습니다. 타입스크립트에게 string타입이라고 명시적으로 말해주고있기 때문입니다

interface Person {
  name: string;
  age: number;
}

let man = {};

man.name = 'bumgu'
man.age = 10;

인터페이스를 생성하고 man이라는 빈 객체를 생성하고 객체의 속성을 추가합니다.
그러면 에러가 발생합니다.

이 에러가 발생하는 이유는 man변수를 선언할 때 빈 객체로 초기화했기 때문입니다.
타입스크립트 컴파일러 입장에서는 해당 객체에 어떤 속성이 들어갈지 알 수 없기 때문에 이후에 추가되는 속성들은 모두 있어서는 안되는 속성으로 간주하기 때문입니다. 이걸 해결하기 위해서는
1. 변수 선언 시점에 속성을 정의
2. 변수의 타입을 Person인터페이스로 정의
가 있습니다.
하지만 이미 운영중인 서비스의 코드나 누군가 만들어 놓은 코드라고 한다면 타입에러를 해결하는데 변경해야 할 코드가 많아질것입니다.
이때 타입단언을 사용해 코드를 변경하지 않고도 타입에러를 해결할 수 있습니다.

let man = {} as Person;

변수를 선언할 때 빈 객체로 선언했지만 이 객체에 들어갈 속성은 Person인터페이스의 속성이라고 타입스크립트 컴파일러에게 말해주는것과 같습니다. 빈 객체로 선언되었지만 Person타입으로 간주됩니다.

Person타입으로 간주되므로 nameage속성을 에러 없이 추가 할 수 있고, 타입단언을 사용하면 타입스크립트 컴파일러가 알기 어려운 타입에 대해 힌트를 제공할 수 있습니다.
또, 선언하는 시점에 nameage소성을 정의하지 않고 추후에 추가할 수 있는 유연함도 가지게 됩니다.

1. 타입단언 문법

function getId(id) {
  return id;
}

let myId = getId('kimm') as number;

이 코드는 as키워드를 사용해 getId('kimm')의 호출 결과를 numeber타입으로 단언합니다. getId()는 파라미터 타입을 따로 정의 하지 않았기 때문에 기본적으로 모든 값을 받을 수 있는 any타입으로 추론됩니다.

타입단언 없는 모습

여기에 as키워드를 사용해 number타입으로 단언하면 myId변수의 타입이 number로 추론됩니다.
타입단언 있는 모습

이와 같이 함수 호출 결과에도 as키워드를 사용해 타입단언을 사용할 수 있습니다.

1-1. 타입단언 중첩

타입단언은 여러번 중첩해서 사용할 수 있습니다. 아래 코드는 변수를 하나 선언하고 숫자 10을 할당한 코드에 타입단언을 두번 사용한 코드입니다.

let num = (10 as any) as number;

num변수는 any로 단언된 상태에서 다시 number타입으로 단언되었기 때문에 최종적으로 number타입이 됩니다.
이와 같이 타입단언을 중첩해 사용할 수 있습니다.

2. 타입단언 주의사항

2-1. as는 구문 오른쪽에서만 사용

  • 잘못사용
let num as number = 10;
  • 올바른 사용
let num = 10 as number;

하지만 이런 단순 변수 선언은 타입표기로 정의하는것이 좋습니다.

let num: number = 10;

2-2. 호환되지 않는 데이터 타입으로는 단언 불가

let num = 10 as string;

타입단언을 이용하면 어떤 값이든 내가 원하는 타입으로 단언할 수 있을 것 같지만 그렇지는 않습니다.

이유는 numberstring타입 사이에는 교집합이 없기 때문입니다.
num의 값은 숫자이기 때문에 강제로 문자열인 string이나 boolean등 다른 데이터 타입으로 변환이 불가능합니다.
하지만 모든 데이터 타입을 수용가능한 any타입으로는 단언이 가능합니다.

2-3. 타입단언 남용하지 않기

타입단언은 코드에 정확한 타입을 선언하면서 타입을 맞추기 보다 간편하고 쉽게 느껴집니다.
하지만 타입단언은 코드를 실행하는 시점에서 아무런 역할도 하지 않기 때문에 타입스크립트의 장점인 에러방지의 효과가 미미해집니다. 타입에러를 쉽게 해결하려고 타입단언을 사용했는데 실행시점의 타입에러는 방지하지 못하기 때문입니다.

3. null아님 보장 연산자 !

null아님 보장 연산자 (non null assertion)은 null타입을 체크할 때 유용하게 사용할 수 있는 연산자 입니다. 타입단언의 한종류이지만 as키워드와 용도가 다릅니다. 값이 null이아닌, 즉 값이 있다고 보장해주는 연산자 입니다.

function shuffleBooks(books) {
  let result = books.shuffle();
  return result;
}

shuffleBooks();

다음과 같은 코드를 작성하면 shuffleBookes()호출 할 때 인자를 넘기지 않았기 때문에 에러가 발생합니다.

그래서 이런 상황을 방지하려고 null값 체크 코드를

function shuffleBooks(books) {
  if (books === null || books === undefined) {
    return;
  }
  let result = books.shuffle();
  return result;
}

와 같이 작성해왔습니다.
이 코드는 books파라미터가 null이거나 undefined면 함수의 로직을 실행하지 않고 종료합니다.
이번에는 앞의 코드에 타입을 입혀 보겠습니다.

interface Books {
	shuffle: Function;
}

function shuffleBooks(books:Books) {
  let result = books.shuffle();
  return result;
}

인터페이스Books에는 Funtion타입인 shuffle속성이 있기 때문에 타입관점에서는 문제가 없습니다. 근데 shuffleBooks()의 인자로 null값도 들어올 수 있게 바꾸려면 어떻게 해야될까요?
유니언 타입으로 해결 할 수 있습니다.

interface Books {
  shuffle: Function;
}
function shuffleBooks(books: Books | null) {
  let result = books.shuffle();
  return result;
}

shuffleBooks();

이러면 에러가 발생합니다.

books파라미터에 null값이 들어올 수 있기 때문에 경고를 해주는 것입니다.
이 에러를 해결하려면 if문을 이용해 null값 체크를 해주면됩니다.

이런 경우에 만약 booksnull 이 아니라는 확신이 있다면 !연산자를 사용해 해결 할 수 있습니다.

books파라미터에는 Books타입이나 null이 들어올 수 있지만
!연산자를 사용해 null값이 아니라는것을 타입스크립트에게 말함으로써 에러를 해결 할 수 있습니다.
하지만 앱을 실행할때 실제로 null값이 들어오면 에러가 발생합니다.
이처럼 as!를 사용한 타입단언이 편리하지만 실행 시점의 에러는 막아주지 못하기때문에 가급적 타입단언 보다는 타입추론에 의지하는것이 바람직합니다.


타입스크립트의 타입단언에 대해 포스팅했습니다.
읽어주셔서 감사합니다.

profile
Software VS Me

0개의 댓글