[타입스크립트] 'is', 'as' 문법 정리

KwakKwakKwak·2022년 11월 1일
2
post-thumbnail

이 글을 작성하는데 참고한 문서:
Stack Overflow - What does the is keyword do in typescript?
Stack Overflow - What does the 'as' keyword do?

요새 타입스크립트를 공부하기 시작했는데, 타입스크립트는 굉장히 복잡하고 어렵다는 느낌을 받고 있다. 그 이유 중 하나인 오늘의 주제 isas 키워드는 정말 찾아보기 전까진 '이게 정녕 프로그래밍 언어의 문법이라고..?' 라는 의심까지 들게 만들었다...

자바스크립트로만 이루어진 코드들을 타입스크립트화 시키는데 굉장히 디테일하게 타입 지정을 해줘야 하면서, 처음 봤을 때 그 뜻을 유추하기 어려운 구어체같은 문법들이 마구마구 쏟아지니... 잘못걸렸다 싶기도 하면서 한편으론 자바스크립트가 얼마나 문법적으로 느슨했는지 깨닫게 되었다.


우선 asis 문법의 사용 예제를 보자.

const instanceOfToDo = (object: any): object is IToDo => {
	return (
		object.constructor === Object &&
		"id" in object &&
		"text" in object &&
		typeof (object as IToDo).id === "number" &&
		typeof (object as IToDo).text === "string"
	);
};

이 코드를 처음 목격했을 때는 정말 뭘 내뱉는 코드인지 전혀 알 길이 없었다.

  • object를 any 타입으로 지정해줬는데 바깥에서 IToDo로 보겠다고..?
  • 그 아래 return문에서는 뭘 이렇게 많이 비교하지..?
  • object as IToDo는 정확히 뭘 말하고자 하는 코드인가..?

이런 의문점이 있었는데, 아마 타입스크립트에 익숙하지 않은 사람들이라면 모두 해봤을 생각일 것이다(아님말고).

'as' 키워드란?

The as keyword is a Type Assertion in TypeScript which tells the compiler to consider the object as another type than the type the compiler infers the object to be.

'as' 키워드란 요약하자면 '컴파일' 단계에서 타입 검사를 할 때 타입스크립트가 감지하지 못하는 애매한 타입 요소들을 직접 명시해주는 키워드라고 볼 수 있다. 예를 들어 아래와 같은 코드가 있다고 하자.

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

이 코드에서 as HTMLCanvasElement 부분이 없다고 가정한다면, 타입스크립트는 그저 myCanvas가 여러 HTMLElement 중 어느 하나의 HTMLElement만을 가질 것이란 사실 밖에 알지 못한다. 이러한 경우에 as HTMLCanvasElement를 적어줌으로써 myCanvasHTMLCanvasElement를 가진다는 것을 알게 되며 에러의 확률을 줄일 수 있게 된다.

위 코드는 as 키워드를 사용하지 않고도 표현할 수 있는데, tsx파일 내에 있다고 가정하고 아래와 같이 쓸 수 있다.

const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");

한 가지 특징은 as 키워드(type assertion)는 오직 컴파일 단계에서만 실행되며 런타임 단계에서는 삭제된 채로 실행된다. 컴파일 단계에서 제대로 넘어갔다면 런타임 단계에서의 에러 발생은 일어나지 않는다는 말이다.

또한 as 키워드를 두 번 쓰면 기존에 A 타입이었던 것을 B 타입으로 바꿀 수도 있는데

const a = (expr as any) as T;

만약 expr의 타입이 원래 string[]였다면 number 타입인 T를 바로 대입하지 못할 것이다. 따라서 expr의 타입을 any로 먼저 바꿔준 다음, 마침내 number 타입으로 assert해줄 수 있는 것이다.


'is' 키워드란?

function isString(test: any): test is string{
    return typeof test === "string";
}

function example(foo: any){
    if(isString(foo)){
        console.log("it is a string" + foo);
        console.log(foo.length); // string function
    }
}
example("hello world");

isString() 함수를 살펴보면 return문의 값이 true일 때, test is string이란 type predicate 구문이 있기 때문에 타입스크립트는 이 함수를 호출한 범위 내에서 isString()의 결과 타입을 string으로 좁힌다. 그러니까 isString() 함수를 거쳐서 return 값이 true라면, type predicate에 적은 말 그대로 '함수가 호출된 범위 내에선 teststring타입으로 보라' 는 것이다. 위 as 키워드와 마찬가지로 is 키워드 역시 컴파일 단계에서만 사용되며 런타임 단계에서는 순수한 js 파일과 동일하게 동작한다.

내가 참고한 스택오버플로우 답변에서는 추가적인 사례를 들면서 설명을 보충하고 있다.

E.g 1

위의 코드는 컴파일 그리고 런타임 단게에서 에러가 발생하지 않는다.

E.g 2

아래의 코드는 컴파일 그리고 런타임 단계에서 에러가 발생하는데, 그 이유는 타입스크립트가 foo의 타입을 string으로 좁혀놓은 상태에서 toExponential()함수가 string 메소드에 속하지 않는다는 에러를 체크하기 때문이다.

참고: toExponential() 메서드는 숫자를 지수 표기법으로 표기해 반환하는 자바스크립트 메소드이다.

  function example(foo: any){
      if(isString(foo)){
          console.log("it is a string" + foo);
          console.log(foo.length);
          console.log(foo.toExponential(2)); // toExponential()은 string method가 아님
      }
  }

E.g. 3

아래의 코드는 컴파일 단계에선 에러가 발생하지 않으나 런타임 단계에선 에러가 발생한다. 그 이유는 타입스크립트는 앞서 말했듯 함수가 호출된 범위 내에서만 타입을 string으로 좁히기 때문이다. 좀 더 정확하게는 foo.toExponential 함수는 {} 밖에 있으므로 타입스크립트에서 string type으로 좁혀놓지 않았기 때문에 컴파일 단계에서 엄격하게 검사받지 않을 것이고, 이는 런타임 단계에서야 일반 자바스크립트가 동작하는 것처럼 string does not have toExponential() method와 같은 에러를 뱉어내며 동작하지 않을 것이다.

function example(foo: any){
    if(isString(foo)){
        console.log("it is a string" + foo);
        console.log(foo.length);
    }
    console.log(foo.toExponential(2)); // type predicate 바깥 = compile 단계 타입 검사 범위 바깥
}

E.g. 4

아래의 코드처럼 test is string이란 type predicate를 사용하지 않고 그저 평범한 타입스크립트처럼 return문의 결과 타입만을 명시해놓을 경우, 타입스크립트는 앞선 코드들처럼 함수가 호출된 범위 안에서 foo의 타입을 string으로 좁혀놓지 않고 또 엄격하게 컴파일 검사를 하지 않을 것이다. 당장 toExponential 메소드 뿐만 아니라 범위 내의 모든 코드들에게도 느슨한 타입 검사가 이루어질 것이며, 이 말은 곧 e.g 3처럼 런타임 단계에 이르러서야 에러를 발견하게 될 것이고, 이는 타입스크립트답지 못한 사용 방식이라 볼 수 있다.

function isString(test: any): boolean{
    return typeof test === "string";
}
function example(foo: any){
    if(isString(foo)){
        console.log("it is a string" + foo);
        console.log(foo.length);
        console.log(foo.toExponential(2));
    }
}

위 네 가지 사례를 살펴보면 is 키워드가 무슨 역할을 하는지 이해할 수 있을 것이다.

결론

  • as 키워드는 Type Assertion(타입 단언)으로, 컴파일 단계에서 타입스크립트가 잘못 혹은 보수적으로 타입을 추론하는 경우 개발자가 수동으로 컴파일러에게 특정 변수에 대한 타입 힌트를 주는 것이다.
  • is 키워드는 Type Guard(타입 가드)로, as가 특정 변수 하나에 국한된 것이라면 is 키워드는 한정된 범위 내의 모든 변수에 대해서 일괄적으로 적용할 수 있는 키워드이다.

0개의 댓글