안녕하세요 YB 이진혁입니다!
이번에는 TS를 공부하면서 마주하게 된 타입 단언을 왜 쓰지 말라고 하는지, 그 개념과 이유를 예시와 함께 더 알아보고자 합니다. 물론 절대 사용하면 안된다는 아니지만 TS를 조금 더 효율적으로 활용하기 위해서 어떤 방법이 있을지 살펴보면 더 좋을 것 같습니다!
🌊 타입 단언(Type Assertion)이란?
타입 단언은 개발자가 해당 타입에 대해 확신이 있을 때 사용하는 타입 지정 방식이다. 다른 언어에서는 타입 캐스팅과 비슷한 개념이며 TS를 컴파일 할 때 특별히 타입을 체크하지 않고, 데이터의 구조도 신경쓰지 않는다.
위에 적어둔 정의를 보면 알 수 있지만 이름 그대로 단언. 이 타입이 확실하다고 TS에게 알려주는 것이다. 어떻게 보면 TS에게 타입을 알려주는 것이니 좋은 것이지 않을까 생각할 수 있다. 하지만 많은 아티클을 살펴보면 TS를 잘 사용하기 위해서는 항상 나오는 말이 있다.
- any 타입을 사용하지 말자.
- 타입 단언 (type assertion)을 사용하지 말자.
첫 번째 any 타입은 TS의 역할을 지우는 것과 비슷하니 지양하라는 이유가 이해가 간다. 그렇다면 타입 단언 사용을 왜 지양하라는 것일까?
🌊 타입 단언(Type Assertion) 기본 - as
타입 단언은 기본적으로 as 키워드를 이용해서 정의할 수 있다.
[변수] as [타입]
설명보다는 예시 코드를 살펴보자.
const user: string = 'Webby';
우리는 기본적으로 타입을 지정할 때 이렇게 : 를 통한 타입 표기 방식을 이용하게 된다. 위 코드의 경우 user라는 변수의 타입이 string이 되는 것이다. 위 코드에 타입 단언을 적용하면 어떻게 될까?
const user = 'Webby' as string;
이렇게 작성하면 기존의 코드처럼 user가 string 타입이 되는 것을 알 수 있다. 그렇다면 기존의 타입 표기 방식을 쓸 수 있는데 언제 타입 단언을 쓰는 것일까?
🌊 타입 단언을 언제 쓰는가?
타입 단언은 타입스크립트의 컴파일러보다 개발자가 더 해당 타입을 잘 알고 있을 때 사용한다. 한마디로 컴파일러가 타입을 잘 추론하지 못하는 경우가 생긴다면 단언을 통해 컴파일러에게 타입에 대한 확신을 주는 것이다.
🤔 타입 단언과 타입 캐스팅은 비슷하지만 다른 개념이다.
타입 단언은 타입을 변경한다는 사실 때문에 타입 캐스팅과 비슷하게 느껴질 수 있다. 하지만 타입 단언이 타입 캐스팅이라고 불리지 않는 이유는 런타임에 영향을 미치지 않기 때문이다. 타입 캐스팅은 컴파일 타임과 런타임에서 모두 타입을 변경시키지만 타입 단언은 오직 컴파일 단계에서만 타입을 변경시킨다.
사용하는 예시는 정말 다양하기 때문에 언제 쓴다 확정하기는 어렵지만 DOM API를 조작할 때 HTML 요소임을 단언하기도 하고, TS가 데이터를 잘 추론하지 못할 때 그리고 error의 타입에 as를 사용하기도 한다.
[ 예시 ]
const div = document.querySelector('div');
console.log(div.innerText); // error!
if (div) console.log(div.innerText)
// 이렇게 HTML 요소임을 단언
const div = document.querySelector('div') as HTMLDivElement;
console.log(div.innerText); // no error
react+ts를 개발하면서 한번씩 봤을 예시도 있다.
const container = document.getElementById("root") as HTMLDivElement;
const root = createRoot(container);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// data를 서버에서 받아올 때 axios error를 Error로 단언했다.
const { status } = handleError(error as Error);
이렇게만 보면 단언이 장점만 있을 것 같지만 그렇지만은 않다. 그렇다면 어떤 단점이 있을까?
🌊 타입 단언의 단점
1️⃣타입 단언을 사용하면 타입 체크가 불가능하다.
interface Person {
name: string;
}
const webby: Person = { name: 'Webby' }; // 타입은 Person
const hyeok = { name: 'Hyeok' } as Person ; // 타입은 Person
타입 선언은 할당되는 값이 해당 인터페이스를 만족하는 검사를 하는데, 타입 단언은 강제로 타입 지정을 해서 타입 체커에게 오류를 무시하라고 하는 것이다.
const webby: Person = {};
// ~~~~~ 'Person' 유형에 필요한 'name' 속성이 '{}' 유형에 없습니다.
const hyeok = {} as Person; // 오류 없음
2️⃣속성 추가에 대한 문제
const webby: Person = {
name: 'Webby',
occupation: 'TypeScript Developer'
// ~~~~~~~ 개체 리터럴은 알려진 속성만 지정할 수 있음, Person 형식에 occupation이 없습니다.
}
const hyeok = {
name: 'Hyeok',
occupation: 'Javascript Developer'
} as Person // 오류 없음
이러한 이유들로 인해 타입 단언을 활용하게 되면 컴파일러가 타입 검사를 하지 않기 때문에 런타임 에러가 발생할 수 있다. 결국 타입 안정성이 떨어질 수 있다는 것이다.
🌊 타입 단언 활용 추가 예시
1️⃣Select 선택되는 값 type 예시
const handleSort = (e: ChangeEvent<HTMLSelectElement>) => {
setSorted(e.target.value as SortKey);
setPage(0);
};
해당 코드는 이전에 프로젝트를 하면서 사용했던 코드 중 일부이다. select의 선택된 값인 e.target.value에 SortKey라는 타입으로 단언을 한 것을 볼 수 있다. SortKey는 select에서 나올 수 있는 값을 가진 type이다.
이 코드에서 단언은 문제가 없을까? 사실 값이 바뀌어야만 handleSort가 실행되기도 하고, e.target.value에 들어올 수 있는 값이 확실하니 크게 문제될 일은 없어보이긴 한다.
하지만 타입 단언을 쓰지 않고 더 안정성을 높이기 위해선 위에 코드를 어떻게 바꿀 수 있을까?
// SortKey 타입인지 확인하는 타입 가드 함수
function isSortKey(value: any): value is SortKey {
return value === 'asc' || value === 'desc';
}
const handleSort = (e: ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value;
if (isSortKey(value)) {
setSorted(value);
setPage(0);
} else {
// ...
}
};
타입 단언의 대체로 많이 알려진 타입 가드를 쓰면 타입 단언을 쓰지 않고 안정성을 높일 수 있다.
2️⃣useParams 예시 (feat. 우테코)
type RouteParams = {
checklistId: string;
};
const ChecklistDetailPage = () => {
const navigate = useNavigate();
const { isModalOpen, modalOpen, modalClose } = useModalOpen();
const { checklistId } = useParams() as RouteParams;
const { data: checklist, isLoading, isError } = useGetChecklistDetailQuery(checklistId);
const { mutate: deleteChecklist } = useDeleteChecklistQuery();
// ...
위 코드에 useParams는 string | undefined 타입을 반환한다. 그런데 위 코드에서는 as로 RouteParams → checklistId: string; 라고 작성하면서 string의 값을 확실하게 단언하는 것을 볼 수 있다.
물론 거의 대부분 string의 값으로 들어오기도 하고, TS가 이 에러를 선언 시에는 잡지 않겠지만 만약 undefined인 경우가 생긴다면 이후에 문제가 생길 가능성도 있지 않을까?
🌊 타입 단언 대신?!
이러한 이유로 타입 단언 대신 타입 선언이나 타입 가드 등의 방법으로 안정성을 높이는 것을 추천한다.
[ 타입 가드 예시 ]
function isString(value: unknown): value is string {
return typeof value === 'string'
}
간단한 예시이긴 하지만 이외에도 많은 타입 가드 방법도 있고, 더 TS의 안정성을 높일 방법은 많다.
물론 무조건 타입 단언을 쓰지마라는 아니지만 최대한 안정성을 높일 방법이 있다면 더 고려해보고 이후에 단언 생각을 해보는 것이 좋지 않을까 생각한다.
[공식 문서]
https://www.typescriptlang.org/docs/handbook/2/narrowing.html
Documentation - Narrowing
Understand how TypeScript uses JavaScript knowledge to reduce the amount of type syntax in your projects.
www.typescriptlang.org
혹시 타입스크립트를 더 깊게 배워보고 싶으시다면 이 책을 추천드립니다! 저도 아직 다 제대로 읽지는 못했지만 해당 아티클 같은 내용들이 많습니다 :) 🥲
'4주차' 카테고리의 다른 글
💥 로딩/에러처리 및 데이터 패칭 라이브러리 (0) | 2024.11.12 |
---|---|
한 번 설정해두면 개발이 편해지는,,, Axios Interceptor (0) | 2024.11.12 |
Error Boundary로 에러 핸들링하기 (0) | 2024.11.12 |
개발 어떤 방식으로 하세요? _ CDD : 컴포넌트 주도개발 (0) | 2024.11.12 |
React Router에서 Outlet과 Layout을 활용하는 방법 (0) | 2024.11.12 |