타입단언 (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
타입으로 간주되므로 name
과age
속성을 에러 없이 추가 할 수 있고, 타입단언을 사용하면 타입스크립트 컴파일러가 알기 어려운 타입에 대해 힌트를 제공할 수 있습니다.
또, 선언하는 시점에 name
과 age
소성을 정의하지 않고 추후에 추가할 수 있는 유연함도 가지게 됩니다.
function getId(id) {
return id;
}
let myId = getId('kimm') as number;
이 코드는 as
키워드를 사용해 getId('kimm')
의 호출 결과를 numeber
타입으로 단언합니다. getId()
는 파라미터 타입을 따로 정의 하지 않았기 때문에 기본적으로 모든 값을 받을 수 있는 any
타입으로 추론됩니다.
여기에 as
키워드를 사용해 number
타입으로 단언하면 myId
변수의 타입이 number
로 추론됩니다.
이와 같이 함수 호출 결과에도 as
키워드를 사용해 타입단언을 사용할 수 있습니다.
타입단언은 여러번 중첩해서 사용할 수 있습니다. 아래 코드는 변수를 하나 선언하고 숫자 10을 할당한 코드에 타입단언을 두번 사용한 코드입니다.
let num = (10 as any) as number;
num
변수는 any
로 단언된 상태에서 다시 number
타입으로 단언되었기 때문에 최종적으로 number
타입이 됩니다.
이와 같이 타입단언을 중첩해 사용할 수 있습니다.
let num as number = 10;
let num = 10 as number;
하지만 이런 단순 변수 선언은 타입표기로 정의하는것이 좋습니다.
let num: number = 10;
let num = 10 as string;
타입단언을 이용하면 어떤 값이든 내가 원하는 타입으로 단언할 수 있을 것 같지만 그렇지는 않습니다.
이유는 number
와 string
타입 사이에는 교집합이 없기 때문입니다.
num
의 값은 숫자이기 때문에 강제로 문자열인 string
이나 boolean
등 다른 데이터 타입으로 변환이 불가능합니다.
하지만 모든 데이터 타입을 수용가능한 any
타입으로는 단언이 가능합니다.
타입단언은 코드에 정확한 타입을 선언하면서 타입을 맞추기 보다 간편하고 쉽게 느껴집니다.
하지만 타입단언은 코드를 실행하는 시점에서 아무런 역할도 하지 않기 때문에 타입스크립트의 장점인 에러방지의 효과가 미미해집니다. 타입에러를 쉽게 해결하려고 타입단언을 사용했는데 실행시점의 타입에러는 방지하지 못하기 때문입니다.
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
값 체크를 해주면됩니다.
이런 경우에 만약 books
가 null
이 아니라는 확신이 있다면 !
연산자를 사용해 해결 할 수 있습니다.
books
파라미터에는 Books
타입이나 null
이 들어올 수 있지만
!
연산자를 사용해 null
값이 아니라는것을 타입스크립트에게 말함으로써 에러를 해결 할 수 있습니다.
하지만 앱을 실행할때 실제로 null
값이 들어오면 에러가 발생합니다.
이처럼 as
나 !
를 사용한 타입단언이 편리하지만 실행 시점의 에러는 막아주지 못하기때문에 가급적 타입단언 보다는 타입추론에 의지하는것이 바람직합니다.
타입스크립트의 타입단언에 대해 포스팅했습니다.
읽어주셔서 감사합니다.