우리가 코드를 짜다보면 에러핸들링이라는 것을 많이 한다. 에러 핸들링은 이 동작에서 무엇인가 잘못 동작했을때 예외처리를 해서 쌩에러 문구가 뜨지 않게끔 하는 것인데,
이러한 에러핸들링을 하려면 매번 그 쓰이는 부분 마다 에러핸들링을 해줘야 하는 번거로움이 있다.
그래서 리덕스를 도입하기전 훅으로 비동기 함수들의 예외처리를 하며 중복코드를 최대한 줄이려고 이러한 로직을 세웠다.
const sendPost = async <T>(url: string, body: Object) => {
try {
const result = await request.post<DefaultResponseBody<T>>(url, body);
console.log(result);
return result.data;
} catch (err) {
catchError(err);
throw err;
}
};
const sendGet = async <T>(url: string, params?: Object) => {
try {
const result = await request.get<DefaultResponseBody<T>>(url, {
params: params,
});
console.log(result);
return result.data;
} catch (err) {
catchError(err);
throw err;
}
};
const sendDelete = async <T>(url: string, body?: Object) => {
try {
const result = await request.delete<DefaultResponseBody<T>>(url, {
data: body,
});
console.log(result);
return result.data;
} catch (err) {
catchError(err);
throw err;
}
};
const sendFormData = async <T>(url: string, formData: FormData) => {
try {
const result = await request.post<DefaultResponseBody<T>>(url, formData, {
headers: { "Content-Type": "multipart/form-data" },
});
console.log(result);
return result.data;
} catch (err) {
catchError(err);
throw err;
}
};
const errorHandling = <T>({ err }: ErrorHandlingProps<T>) => {
console.log(err.response, "errorHandling");
if (!err.response) throw err;
const { status } = err.response.data;
if (!ERROR_CODES[status]) {
// message.error(EXCEPT_ERRORCODES_MSG);
return;
}
if (status === "C401") {
return;
}
if (status === "U402") {
return;
}
if (status === "T401" || status === "G403") {
// message.error(ERROR_CODES[status].message, {
// onClose: () => {
// window.localStorage.removeItem("accessToken");
// window.localStorage.removeItem("expireAtAccessToken");
// window.localStorage.removeItem("user");
// window.location.href = LOGIN_PAGE_URL;
// },
// });
} else {
// message.error(`${ERROR_CODES[status].message}`);
}
};
const catchError = (err: AxiosError<DefaultResponseBody<any>>) => {
if (axios.isAxiosError(err)) {
errorHandling({ err });
} else {
// message.error(EXCEPT_ERROR_MSG);
console.error(err);
}
};
보면 중복되는 로직도 많고 굳이 에러핸들링을 두단계에 걸쳐서 하는 이상한 코드라서 디버깅 할때 에러가 어디에서 처음 걸러지는지 확인하고 하는 필요없는 작업을 해야한다.
이때 왜 이런 비효율적인 로직을 짰냐면, message 라고 보이는 부분이 훅이였기 때문이다.
import {useAlert} from "react-alert"
const message = useAlert();
message.error("에러 메세지");
react-alert
라는 라이브러리는 커스텀 confirm 창을 쉽게 만들수 있게 해준다. 하지만 기반이 contextAPI 엿기 때문에 훅으로 구현이 되버리기 때문에 사용이 저 한줄 써서 사용자에게 커스텀 confirm 창을 띄울 수도 있지만 훅규칙을 지켜야 하는 제한사항도 생긴다.
따라서 나는 비동기 함수를 구현할때 해당 함수들을 훅으로 구현할 수 밖에없엇고 에러핸들링도 훅안에 넣어야 했다.
엄청나게 많은 api들의 기본적인 에러들을 한번에 거르고 싶어서 굳이 저렇게 두단계로 나눈것이다.
그리고 response의 정상적인 응답에 따른 핸들링은 컴포넌트 단위에서 하려고 말이다.
postActions.ts
export const like = createAsyncThunk("post/like", async (userId: number) => {
await request.post(LIKE_API_URL, { userId });
});
PostModal.tsx
const handleClickLike: MouseEventHandler<HTMLButtonElement> = async (e) => {
try {
e.preventDefault();
await dispatch(like(props.id));
setIsActiveLike(true);
} catch (error) {
dispatch(asyncErrorHandle(error));
}
};
Errorboundary
class Errorboundary extends Component<Props, States> {
state: States = {
notificationElement: null,
};
componentDidCatch(error: Error) {
this.props.updateAlert({
name: error.name,
message: error.message,
});
}
componentDidMount() {
this.setState({
notificationElement: document.getElementById("notification"),
});
}
render() {
const alert = this.props.alert;
return (
<>
{alert.length !== 0 &&
this.state.notificationElement &&
ReactDOM.createPortal(
<AlertMessage />,
this.state.notificationElement
)}
{this.props.children}
</>
);
}
}
에러바운더리와 리덕스를 이용해서 로직이 간결해졌다.
개선점을 말해본다.
즉, 모든 에러를 store에 모아서 중앙 집중화를 통해 에러를 관리 할수 있게 되어 다른 코드를 넣을 것 없이 try catch 에 dispatch 한번이면 비동기 함수부터 컴포넌트 에러까지 모든 에러를 핸들링 하게 된다.
심지어 확인 버튼을 누르면 동작하는 함수까지 구현가능해서 이전에 훅으로 구현할 때랑 기능적인 차이는 없으나 코드관리는 훨씨 간편해졌다.
코드를 짜기 훨씬 편해진접은 바로 5번이였다. 더이상 컴포넌트 최상단, 훅안에서만 사용해야한다같은 규칙을 버릴 수 있어, 원하는 코드를 이상적으로 로직화 할 수 있었다.
물론 이게 좋은 로직이라고 자신을 할 수는 없다. 하지만 이전보다 더 나은 전략이라고는 확신한다. 추후 클린코드를 공부하면서 에러 핸들링과 로직에 대해서 깊게 고민해볼 예정이다.