프로그램 실행 중 어떤 원인에 의해 오작동하거나 비정상 종료된 경우,
이를 초래하는 원인을 프로그램 에러 또는 오류라 한다.
에러, 버그, 예외 등은 대개 비슷하게 예상치 못한 문제 정도의 의미로 사용되는데 이를 구분해보면 내가 핸들링하고자 하는 대상이 무엇인지 알 수 있다.
여기서 지금 다루고자 하는 것은 “에러”이다. 어떻게 하면 에러를 잘 다루어 궁극적으로 완성도 높은 프로그램을 만들 수 있을까? 이를 프론트엔드의 관점에서 알아보도록 하자.
프론트엔드에서 자주 발생하는 에러에는 다음과 같은 종류가 있다.
이외에도 인증 에러, 브라우저 에러, OS 업데이트에 의한 에러, 잘못된 접근으로 인한 에러, 악의적 목적의 접근에 의한 에러 등이 있을 수 있다.
에러가 발생할 수도 있는 지점을 예외 구문으로 설정하여 프로그램이 예상치 못하게 종료되거나 기능을 못하는 상황을 방지
에러 핸들링을 위해 프론트엔드에서는 다음과 같은 방법들을 사용할 수 있다.
이러한 에러 핸들링을 위해서는 프론트엔드단에서 적절한 상황에 적절한 대처를 할 수 있도록 설정해주어야 한다. 하지만 팀 프로젝트에서 여러 개발자가 각자의 방식으로 에러를 처리하게 되면 또는 전혀 하지 않게 되면 유지보수하기 정말 어려울 뿐더러 어디서 에러가 날지 찾기 어려운 코드가 된다. 따라서 일관성있는 에러 핸들링은 서비스 완성도에 크게 기여한다.
그렇다면 위의 목표를 위해 어떻게 하는 것이 좋을까?
필자는 아래와 같은 방법을 사용했다.
각각의 기능에서 발생 가능한 에러와 status code를 정의한다.
정의에 따라 React-query의 onError 프로퍼티를 통해 처리한다.
const { mutate } = useMutation(uploadImage, {
onError: (err: { response: { status: number } }) => {
if (err.response.status === 413) {
toast.error('이미지 사이즈가 너무 커요. 다른 사진으로 다시 시도해주세요. ');
} else {
toast.error('이미지 업로드에 실패했어요. 다시 시도해주세요.');
}
},
});
서비스 내에서 throw 되었거나 예상 불가한 에러는 ErrorBoundary에 잡히도록 감싼다.
import React from 'react';
interface IErrorBoundaryState {
hasError: boolean;
}
export class ErrorBoundary extends React.Component<any, IErrorBoundaryState> {
constructor(props: any) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <h1>문제가 발생했어요.</h1>;
}
return this.props.children;
}
}
이외의 서버관련 에러는 axios의 interceptor를 활용해 처리한다.
apiClient.interceptors.response.use(
(res) => res.data,
(err) => {
const { status } = err.response;
if (status >= 500) {
toast.error('앗! 오류가 발생했어요.' + '\n' + '잠시 후에 다시 시도해보세요.');
}
},
);
React에서의 에러 핸들리에 대해 조사하다보니 다양한 설계와 케이스들이 존재했다. 내 프로그램에는 어떤 방식이 적절할지 고민해보고 점차 발전시켜나가면 좋을 것 같다.
좋은 글이라 재밌게 보고 있었는데
작성자분이 현조 님!!
저는 익명의 팬입니다!
벨로그로 오셨군요 흐흐
저도 최근에 react query 에서 에러 핸들링 작업하고 있어서 더 집중해서 봤네요!
늘 응원합니다!!