쓰지말라는 Type Assertions(타입 단언) 은 왜 있나?

myung hun kang·2023년 6월 11일
1
post-thumbnail

서론

typescript

Typesciprt를 처음 배울 당시가 생각난다...

javascript에 타입을 얹은 것이기에, 전혀 새로운 언어를 학습하는 것이 아니기에 처음 시작은 어렵지않게 시작할 수 있었다.

하지만 Typescript를 잘 사용하는 것은 쉽지 않은 일이다.

특히나 많은 개발자 분들의 얘기를 주워들어보면

typescript를 잘 사용하기 위해서는 다음의 것을 하지 말아야 한다고들 한다.

  • any 타입을 사용하지 말 것!
  • 타입 단언 ( type assertion )을 사용하지 말 것!

처음 배울 때는 위 두가지를 최대한 하지않고 개발하려고 노력했다. 그리고 사용하면 큰~~일 나는줄 알았다.

뭔가 사용하면 " 에이~~ typescript 잘 못 쓰네~ " 하는 소리를 들을까 겁부터 났었다.

지금은 그래도 typescript가 좀 익숙해지긴 했는데...

여기서 궁금한 것이 생겼다.

아니, 사용하면 안되는 걸 왜 만들어둔거여???
쓸데가 있으니까 만든거아녀? 다 ~~ 쓰지말라고하면 없애버리지 왜 있는겨??

이번 글에서 알아보도록 하겠다.

타입 단언의 단점

타입 단언은 다음과 같은 상황에 사용할 수 있다.

let person = {}

person.sayhi = "Hi"
person.age = 13

js의 경우 위 코드는 문제가 없다. 하지만 ts의 경우에는 문제가 있다.
아마 다음과 같은 에러를 표시할 것이다.

Error: property 'sayhi' does not exist on {}
Error: property 'age' does not exist on {}

typescript는 person은 빈 객체 만을 가지는 변수라고 인식하여 위 두가지를 정의할 수 없는 것이다.

이런 경우 타입 단언을 사용하면 쉽다.

interface human {
	sayhi: string;
  	age: number;
}

let person = {} as human;

person.sayhi = "Hi"
person.age = 13

person 객체는 이제 빈 객체이지만 human이라는 타입을 가지기 때문에 다음의 값들을 할당할 수 있게 된다.

 위 as 로 타입을 단언한 부분은 .tsx 파일이 아닌경우에는 <>를 이용해서 <human>{} 과 같은 식으로 단언할 수도 있다. 

이와같은 타입 단언은

타입스크립트 컴파일러에게

"다음의 값은 이런 타입을 가진거야 그러니까 에러를 띄우지마!"

라고 말하는 것이다.

그러니 위와 같은 상황을 편하게 넘어갈 수도 있지만 뜻하지 않게 에러를 만들수도 있다.

위 예시에서 만약 타입 단언 후에 person에 sayhi나 age를 할당하지 않았다고 해보자

interface human {
	sayhi: string;
  	age: number;
}

let person = {
	// 아무것도 안 넣음
} as human;

console.log(person.sayhi)

무슨 일이 일어날까?

당연히 에러가 발생할 것이다. 하지만 런타입 환경에서 에러가 날 것이다.

타입 단언을 사용했기 때문에 오류가 난 코드를 컴파일 단계에서 발견하지 못하고 런타임 단계까지 가서야 발견하게하였다.
이는 타입스크립트를 사용하는 큰 장점을 무시한 셈이다.

이러한 이유에서 우리는 타입단언을 사용하지 말아야한다.

근데 왜 있냐고...

많은 경우에 우리는 타입 단언의 사용을 지양해야하지만

적절하게 사용하게되는 상황들이 있다.

대표적으로는 HTML요소에 접근할 때이다.

create-react-app으로 typescript react 앱 셋팅을 한사람은 모두 봤을 것이다.


const container = document.getElementById("root") as HTMLDivElement;
const root = createRoot(container);

root.render(
  <React.StrictMode>
        <App />
  </React.StrictMode>
);

맨 첫줄에 떡하니 사용하고 있다.

그렇다 위에서 document.getElementById("root") 를 변수에 할당하면 HTMLElement이거나 null 일 수 있다고 나온다.

하지만 우리 프로젝트의 특성상 위 root id를 가지는 요소는 적확히 HTMLDivElement 일 수 밖에 없다는 것을 알고있다.

index.html에 그렇게 작성을 했으니 당연하다.

이처럼 html 요소에 접근할 때에는 사용할 수 밖에 없다.

한가지 예시를 더 들어보자

const Modal: React.FC = () => {
  return (
    <>
      {createPortal(
        <Backdrop />,
        document.getElementById("backdrop") as HTMLDivElement
      )}
      {createPortal(
        <ModalWrapper />,
        document.getElementById("overlay") as HTMLDivElement
      )}
    </>
  );
};

react 의 createPortal 을 이용해서 Modal 컴포넌트를 만든 예시이다.

index.html에 미리 <div id=backdrop /><div id=overlay /> 를 만들어 두고
위와 같이 사용한다면 타입 단언을 사용해야한다.

또 다른 경우로는

stringify된 JSON 값을 pasing 해서 사용하는 경우이다.

브라우저 스토리지나 기타 여러 경우에 stringify된 json객체를 가져와 JSON.parse해서 사용하는 경우가 있다.

이때 JSON.parse를 거치고 난 값은 any 타입으로 나온다.

필자의 경우에는 searchParam에 있는 모든 값을 사용할 때 그랬다.
이 경우에는 any가 아닌 [k: string]: string 였지만...

여하튼 코드는 다음과 같다.

const deserialize = <T>(data: string): T => JSON.parse(data) as T;

const jake = deserialize<Person>('{"name":"Jake", "age":24, "occupation": "artist"}');
 

위처럼 파싱한 후에 타입을 갖게하기 위해서 사용할 수 있다.

사실 위 경우는 다른 블로그의 글을 참고했다. 직접 확인하지는 않았지만 신빙성이 있어보여서 가져왔다.
(주소 : https://www.bytelimes.com/why-you-should-avoid-type-assertions-in-typescript)

결론

아직 타입스크립트에 익숙하지 않는 개발자들에게는 많은 경우 타입단언은 안 사용하는것이 좋다.

하지만 절대 사용하지 말아야 하는것이 아닌 잘 사용해야 한다는 것을 알아가는 계기가 되었으면 한다.

추가적으로

타입 단언을 사용하고 싶은 경우 다음의 방법을 사용해보길 바란다.

Type Annotations

interface human {
	sayhi: string;
  	age: number;
}

let person: human = {} 

이처럼 type annotation을 사용하면
person객체에 어떤 값이 비었는지 에러를 띄워주기 때문에 오류없이 사용이 가능하다.

Type Guards

타입 가드를 사용해서 타입을 좁혀서 사용할 수 있는 경우가 분명 있을 것이다.

function isString(value: unknown): value is string {
	return typeof value === 'string'
}

아주 간단한 예시지만 거의 이와같은 식으로 만들면 된다.

이와같은 방식을 사용하면 타입 단언을 쓰고 싶은 곳에 단언없이 사용할 수 있을 것이다.

다른 방법이나 예시를 보고 싶으면 다음 공식문서를 확인하자.

https://www.typescriptlang.org/docs/handbook/2/narrowing.html

profile
프론트엔드 개발자입니다.

0개의 댓글