unknown 타입의 error 객체를 만나다
커피빈 클론 중 로그인 폼을 제출했을 때, 실패 시 error status에 따라 적절한 alert을 띄우려고 한다.
이 때 try catch문으로 erorr를 다루는데 error에서 자꾸만 빨간 줄이 떴다.
error 뒤에가 보이지 않는데, 처음에는 error의 타입을 any로 줬다. 하지만 eslint에서 에러를 내기도 했고 (Unexpected any. Specify a different type 라고 off를 하지 않았다.), 되도록 any는 지양하려고 해서 하라는대로 unknown을 주었다.
그런데 error의 타입을 unknown으로 지정했는데도 자꾸만 error 타입은 unknown이야! 이런다.
아니 unknown으로 했잖아 왜 그러는데! 하다가 예전에 공부한 내용을 찾아봤는데...
과거의 내가 실패의 결과값 타입은 unknown으로 고정되어 있기 때문에 catch 메서드에서는 타입 좁히기를 사용하라고 말했다.
그러니까 저 error 값은 unknown으로 고정되어 있기 때문에 타입을 좁혀 사용해야 하는 것이었다.
그거 어떻게 하는 건데?...
구글링을 통해 3가지 방법을 알게 되었다.
1. any로 강제 변경
error: unknown이 아니라 error: any로 강제 변경하는 방법이 있다.
이 방법은 우선 eslint에 any를 설정할 수 없도록 해놔서 에러가 뜨기도 했고, 되도록 any는 지양하는 것이 좋다고 해 선택하지 않았다.
2. 새로운 타입을 정하고 타입 단언
현재 error 객체의 타입이 unknown이니까, 새로운 타입을 만들고 이 타입이라고 지정해주는 방법이다.
error 객체는 axios의 Error 객체를 상속 받고, 그 아래에 response 프로퍼티와 data, status를 추가해주면 된다.
혼자 이것저것 찍어보며 만져보는데 일단 error가 Error의 인스턴스인 것을 알게 됐다.
이 Error 객체의 종류로는 ReferenceError, SyntaxError, TypeError나 AxiosError 객체 등이 있다.
기본적으로 모든 에러 객체는 Error 객체를 상속하므로 instanceof를 사용하면 true가 되는 것이었다.
하지만 Error 타입에는 response 프로퍼티가 존재하지 않는다고 한다.
위에서 말한대로 Error 타입을 확장 시켜 response 프로퍼티를 가진 새로운 타입을 만들어보자.
interface CustomError extends Error {
response?: {
data: object;
status: number;
headers: string;
};
}
...
try {
...
} catch (error: unknown) {
const errorObj = error as CustomError;
console.log(errorObj);
console.log(errorObj.response);
console.log(errorObj.response?.data);
console.log(errorObj.response?.headers);
console.log(errorObj.response?.status);
Error를 extends 해서 만든 CustomError로 타입을 지정해줬다.
response에 ?를 붙이지 않았는데, response가 없는 에러가 나올 수도 있으므로 붙여주는 게 더 안전한 것 같다.
이렇게 response에 옵셔널을 붙이면 response가 있다고 가정하고 접근하는 data, headers, status에도 옵셔널을 붙여줘야 한다.
이 방법이 좋다고 생각했는데 이 방법에도 단점이 있었다.
바로 as 연산자로 타입을 단언하는 것도 썩 좋은 행위는 아니라는 관점이 있기 때문이다.
결국 as 연산자로 개발자가 직접 타입을 단언해주어야 하는데 장기적인 관점에서 이 과정에서 실수가 발생할 수 있고, 유지보수에 어려움이 생기게 된다. 지금이야 한 건이라 할 수 있어 보이지만 만약 이런 에러가 100만개 있다면... 역시 개발은 극단적으로 생각해야 한다는 걸 다시 깨달았다.
아무튼 그래서 any도, 타입 단언도 아닌 타입 좁히기로 에러를 처리하기로 했다.
3. 타입 가드
CustomError로 타입 단언을 하지 말고 타입을 좁혀서 쓰려고 하는데 또 에러가 났다.
CustomError는 타입만 참조하지만 여기서는 지금 값으로 쓰이고 있다고 한다.
이게 무슨 소리인지 혼란이 오는데, 이전에 공부했던 걸 다시 끄집어 봤다.
interface든 type이든 똑같이 오류가 뜨는데, 생각해보니 instanceof 연산자는 인스턴스, 즉 클래스를 기반으로 한 인스턴스인지 확인하는 연산자였다. 그렇기 때문에 오직 클래스의 인스턴스에서만 사용할 수 있었다.
또 더 나아가 interface는 컴파일을 하면 JS 파일에 코드가 남지 않아 런타임 시 확인할 수 없다는 것도 새로 깨닫게 되었다.
interface나 type은 타입 스크립트의 개념이기 때문에, JS로 컴파일해도 남을 수 있도록 JS에도 있는! 클래스 문법을 사용해야 한다.
즉 CustomError는 클래스로 확장해주어야 하는 것이었다.
비로소 빨간줄 없이 Error 객체를 다룰 수 있게 되었다. 🥹
error 객체와 unknown 타입, interface/type/class에 대해 다시 이해할 수 있는 좋은 계기였다!
isAxiosError에 대해서도 새로 알게 되어서 이 부분도 개발하면서 뜯어봐야겠다...
📌 참고
'❔ TypeScript' 카테고리의 다른 글
[TypeScript] Geolocation API와 구글 맵 API를 사용한 역지오코딩 (0) | 2024.05.28 |
---|---|
[TypeScript] TanStack Query 사용 시 Type '{}' is not assignable to type 'ReactNode'. 에러 해결 (0) | 2024.05.12 |
[TypeScript] Object is possibly 'undefined' 에러 해결 (0) | 2024.03.21 |
[TypeScript] yarn + Vite + TS로 웹 개발 시작하기 (5) | 2024.03.07 |