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)
무슨 일이 일어날까?
당연히 에러가 발생할 것이다. 하지만 런타입 환경에서 에러가 날 것이다.
타입 단언을 사용했기 때문에 오류가 난 코드를 컴파일 단계에서 발견하지 못하고 런타임 단계까지 가서야 발견하게하였다.
이는 타입스크립트를 사용하는 큰 장점을 무시한 셈이다.
이러한 이유에서 우리는 타입단언을 사용하지 말아야한다.
많은 경우에 우리는 타입 단언의 사용을 지양해야하지만
적절하게 사용하게되는 상황들이 있다.
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)
아직 타입스크립트에 익숙하지 않는 개발자들에게는 많은 경우 타입단언은 안 사용하는것이 좋다.
하지만 절대 사용하지 말아야 하는것이 아닌 잘 사용해야 한다는 것을 알아가는 계기가 되었으면 한다.
타입 단언을 사용하고 싶은 경우 다음의 방법을 사용해보길 바란다.
interface human {
sayhi: string;
age: number;
}
let person: human = {}
이처럼 type annotation을 사용하면
person객체에 어떤 값이 비었는지 에러를 띄워주기 때문에 오류없이 사용이 가능하다.
타입 가드를 사용해서 타입을 좁혀서 사용할 수 있는 경우가 분명 있을 것이다.
function isString(value: unknown): value is string {
return typeof value === 'string'
}
아주 간단한 예시지만 거의 이와같은 식으로 만들면 된다.
이와같은 방식을 사용하면 타입 단언을 쓰고 싶은 곳에 단언없이 사용할 수 있을 것이다.
다른 방법이나 예시를 보고 싶으면 다음 공식문서를 확인하자.
https://www.typescriptlang.org/docs/handbook/2/narrowing.html