ํ๋ก์ ํธ์ ๋ฆฌ์กํธ ์ฟผ๋ฆฌ๋ฅผ ๋์ ํ๋ฉด์ ์ด๊ฒ์ ๊ฒ ํด๋ณด๊ณ ์ถ์๊ฒ ๋ง์๋ค. ์๋๋ ๋ฆฌ์กํธ ์ฟผ๋ฆฌ์ ์ฅ์ ์ด๋ผ๊ณ ํ๋ฉด ์์ฃผ ๋์ค๋ ๊ฒ๋ค์ด๋ค.
- ํจ์นํด์จ ๋ฐ์ดํฐ๋ค์ ์บ์ฑ
- staleํ ๋ฐ์ดํฐ์ธ์ง ์๋์ง๋ฅผ ๊ตฌ๋ถํด์ ํญ์ ์ ์ ํ ๋ฐ์ดํฐ๋ก ์ ์ง
- ์๋ฒ ์ํ์ ํด๋ผ์ด์ธํธ ์ํ์ ๋ถ๋ฆฌ
- success์ error ๋ฑ์ ํจ์นญ ์ํ๋ฅผ ๊ด๋ฆฌ
ํนํ ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์์ด์ ๊ณ ๋ฏผ์ ๋ ํด๋ณด๊ณ ์ถ์๋ค. axios๋ก ๋งค๋ฒ ํจ์นญํด์ฌ๋ ํธ๋ผ์ด ์บ์น๋ก ๊ฐ์ธ์ ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ๋ ์ฝ๋๋ฅผ ์์ฃผ ์ฌ์ฉํ๋ค. ๊ทธ๋ด ๋๋ง๋ค ๋น์ทํ ๋ก์ง(๋ณดํต์ ๊ทธ๋ฅ ์ฝ์๋ก๊ทธ..)์ ์ฌ๊ธฐ์ ๊ธฐ ์์ฑํ๊ฑฐ๋, ์ฌ๋ฌ ํ์ด์ง์์ ๊ณตํต์ผ๋ก ์ฌ์ฉ๋์ด์ผ ํ๋ ๋ก์ง๋ ์ค๋ณต์ผ๋ก ์ฐ์ด๋ ๊ฒฝ์ฐ๊ฐ ๋ง์๋ค. ์ ๋ถํฐ ๊ณ ๋ฏผ์ ๋ช๋ฒ ํ์๋ ๋ถ๋ถ์ด์๋๋ฐ ๋ง์นจ ์ฐธ๊ณ ํ ๋งํ ์ข์ ๋ ํผ๋ฐ์ค๋ค์ด ์์ด์ ํ๋ก์ ํธ์ ๋ฃ์ด๋ณด๊ณ ์ ํ๋ค. ์ฐธ๊ณ
๋ฐฑ์๋ ์น๊ตฌ์๊ฒ ์ด๋ฒ์ ์๋ฌ์ฒ๋ฆฌ๋ฅผ ์ด๋ฌ์ฟต์ ๋ฌ์ฟต ํด๋ณด๊ณ ์ถ๋ค๊ณ ๋งํ๋๋ ์ด๋ ๊ฒ ๋ด๋ ค์ฃผ์๋ค. ๋ชจ๋ ์๋ฌ์ ๋ํด์ ๊ณตํต์ ์ผ๋ก dto์ ์ ์ํด์ฃผ๊ณ , ์ค์จ๊ฑฐ๊น์ง ์น์ ํ๊ฒ. ์๋ฌ ์ฝ๋๋ง๋ค ์ค๋ ํ์์ ํ๋์ ๋ณผ ์ ์๋ค. http ์ํ์ฝ๋๋ฅผ ๋ฐ๋ฅด๊ณ ๊ทธ์ ๋ง๊ฒ ์๋ฌ๋ฉ์์ง๊ฐ ๋ฐ๋ผ์จ๋ค.
400 ์๋ฌ์๋ ๋ค์ํ ์ด์ ๊ฐ ์์ ์ ์๋ค. BadRequest ์ ๋ํด์ "Auth-0002"์ ๊ฐ์ด ์ปค์คํ ํ code๋ฅผ ๊ฐ์ด ๋ด๋ ค์ค๋ค. ValidationError๋ ๊ฒ์ฆ์ค๋ฅ๊ฐ ๋ ํ๋๋ฅผ ๊ฐ์ด ๋ด๋ ค์ค๋ค. ๋๊ฐ์ 400์ด๊ธฐ ๋๋ฌธ์ ์๋ฌ ์ฝ๋๋ง ๋ณด๊ณ ์ด๋ค ์๋ฌ์ธ์ง ํ๋จํ๊ธฐ ์ด๋ ต๋ค.
export interface ICustomError {
error: string;
statusCode: number;
message: string;
code: string;
}
export interface IValidationError extends ICustomError {
validationErrorInfo: { [key: string]: string };
}
export type TCustomErrorResponse = {
method: string;
timestamp: number;
statusCode: string;
path: string;
error: ICustomError;
};
์๋ฌ ๊ฐ์ฒด์ ํ์
์ ์ด๋ ๊ฒ ์ ์ํ๋ค. ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด ์ฐ์ React Query์ onError
๋ฅผ ํตํด ์๋ฌ ๊ฐ์ฒด๋ฅผ ๋ฐ๋๋ค. ๊ทธ ์ดํ์ ์๋ฌ ๋ฆฌ์คํฐ์ค๋ฅผ ๋ฏ์ด๋ณด๊ณ ๊ฐ๊ฐ ์๋ฌ๋ง๋ค ์ง์ ๋ ์์
์ ์ํํ๋ค.
์๋ฌ ์ฒ๋ฆฌ ํ๋ฆ์ ์ผ๊ด์ ์ผ๋ก ์ ์ ์งํ๊ธฐ ์ํด์ ๊ด๋ จ ์ฝ๋๋ค์ ๋ถ์ฐ์ํค์ง ์๊ณ ๊ฐ๊ธ์ ๋ชจ์์ ์์ฑํฉ๋๋ค. ํนํ React๋ฅผ ์ด์ฉํ๊ณ ์์ผ๋ฏ๋ก Hook์ผ๋ก ์๋ฌ ์ฒ๋ฆฌ ํ๋ฆ์ ์ฃผ์ ๋ก์ง์ ์์ฑํฉ๋๋ค.
์๋ฌ ์ฒ๋ฆฌ Hook
Query Client์ ๊ณตํต์ ์ผ๋ก ์๋ฌ ํธ๋ค๋ฌ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด ๋ฆฌ์กํธ ํ ์ ์ฌ์ฉํ๋ค.
const useApiError = () => {
const { openErrorModal } = useErrorModal();
const handleError = useCallback((axiosError: AxiosError) => {
const errorResponse = axiosError.response?.data as TCustomErrorResponse;
const error = errorResponse.error;
const status = error.statusCode;
switch (status) {
// BadRequestException | ValidationError
case 400:
if (isValidationError(error)) {
console.log(error.validationErrorInfo);
} else {
openErrorModal(error);
}
break;
// ๊ณผ๋ํ ์์ฒญ์ ๋ณด๋ผ ์
case 429:
openErrorModal(error);
break;
// ๋ฌธ์๋ฉ์์ง ๋ฐ์ก ์คํจ
case 500:
defaultHandler(error);
break;
default:
defaultHandler(error);
break;
}
}, []);
return { handleError } as const;
};
export default useApiError;
handleError ํจ์์์ ์๋ฌ ๊ฐ์ฒด๋ฅผ ๋ฐ์ ์ฝ๋๋ณ๋ก ์ง์ ๋ ๋ก์ง์ ์ํํ๋ค. ํ๋ก์ ํธ์์ ์ฌ์ฉ์๊ฐ ๋ด์ผํ ์๋ฌ๋ ์ ๋ถ ๋ชจ๋ฌ์ ํตํด ๋ณด์ฌ์ฃผ๊ณ ์๋ค. 400๊ณผ 429์๋ฌ๊ฐ ์ด์ ํด๋น๋จ. (ํ๋ก ํธ์ค๋ ๋จ์์ ํ๋ฒ ๋ง๊ณ ์์ฒญ์ ๋ณด๋ด๊ธฐ ๋๋ฌธ์) ์ ์์ ์ธ ๊ฒฝ์ฐ๋ผ๋ฉด ๋ฐ ์ผ๋ ์๊ณ ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ ธ์๋ ์๋๋ ๋ฐธ๋ฆฌ๋ฐ์ด์ ์๋ฌ๋ ๊ทธ๋ฅ ์ฝ์์ ๋์ฐ๋๊ฑฐ๋ก ์ฒ๋ฆฌํ๋ค. ์ฌ์ค ํ์ ๊ฐ๋๋ฅผ ์ฌ๊ธฐ์ ํ๋ฒ ์จ๋ณด๊ณ ์ถ์์.
function isValidationError(error: any): error is IValidationError {
return (error as IValidationError).validationErrorInfo !== undefined; // T of F
}
๊ฐ์ฒด๋ฅผ ๊น์ ๋ด๋ถ ๊ฐ์ ๋ณด๊ธฐ ์ ์ ํ์ ๊ฐ๋๋ฅผ ํตํด error๊ฐ validationError ํ์ ์ธ์ง ํ์ธํ๋ ํจ์์ด๋ค. if๋ฌธ์ผ๋ก ์ด๋ฆฌ์ ๋ฆฌ ํ์ธํด๊ฐ๋ฉด์ ๋๋ฆฌ๋๊ฒ๋ณด๋ค ํจ์ ๊ฐ๋ ์ฑ์๋ค.
export type TErrorCode =
| 'Auth-0000'
| 'Auth-0001'
| 'Auth-9000'
| 'Auth-5000'
| 'Auth-0002';
const errorMessage = {
'Auth-0000': '์ธ์ฆ๋ฒํธ์ ๊ธฐํ์ด ๋ง๋ฃ๋์์ด์',
'Auth-0001': '์ธ์ฆ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค',
'Auth-9000': '์ ์ ๋ค์ ๋ค์ ์๋ํด์ฃผ์ธ์',
'Auth-5000': '๋ฌธ์ ๋ฐ์ก์ ์คํจํ์ด์\n์นด์นด์ค ์ฑ๋๋ก ๋ฌธ์์ฃผ์ธ์',
'Auth-0002': '์ด๋ฏธ ๊ฐ์
๋ ๋ฒํธ์
๋๋ค',
};
const useErrorModal = () => {
const { openModal, closeModal } = useModal();
const openErrorModal = (error: ICustomError) => {
const code = error.code as TErrorCode;
console.log(code);
openModal({
modalType: 'Notice',
modalProps: {
onClick: () => {
closeModal();
},
type: '์๋ฌ์ฒ๋ฆฌ',
errorMessage: errorMessage[code],
},
});
};
return { openErrorModal } as const;
};
export default useErrorModal;
useErrorModal์์๋ ์ปค์คํ ์๋ฌ ๊ฐ์ฒด๋ฅผ ๋ฐ์ ์๋ฌ ์ฝ๋์ ๋ง๋ ๋ชจ๋ฌ์ ๋์ฐ๋ ํจ์๋ฅผ ๋ฆฌํดํ๋ค. ์๋ฌ์ฉ ๋ชจ๋ฌ์ ๊ฐ์ ๋์์ธ์ ๋ฌธ๊ตฌ๋ง ๋ค๋ฅด๊ฒ ๋ค์ด๊ฐ๋ค.
๋ฆฌ์กํธ ์ฟผ๋ฆฌ Default onError
QueryClient๋ฅผ ์ด๊ธฐํ ํ ๋ ๋ชจ๋ ์ฟผ๋ฆฌ๋ฌธ์ ๋ํดํธ๋ก ์ ์ฉ๋๋ ์ค์ ์ ๋ฃ์ด ์ค ์ ์๋ค.
const queryClient = new QueryClient({
defaultOptions: {
onError: handleError,
},
})
๊ฐ๋จํ ์ด๋ฐ ์์ผ๋ก ํ๋ฉด ๋จ.
ํ์ง๋ง ๋ด ๊ฒฝ์ฐ์ onError
์ด๋ฒคํธ์ ์ปค์คํ
ํ
์์ ๋ฐ์์จ handleError ํจ์๋ฅผ ์ ๋ฌํด์ผ ํ๋ค. ๊ทธ๋์ index.tsx์์ App์ ๊ฐ์ธ๊ณ ์๋ <QueryClientProvider/>
๋ฅผ App.tsx ๋ด๋ถ๋ก ๊ฐ์ ธ์ ๊ฐ์ธ๋ ๊ฑธ๋ก ์์ ํ๋ค. ๊ทธ๋ฌ๋๋ ์ฌ๊ฑธ, ๋ชจ๋ฌ์ ๋์์ผํ๋ ์ํฉ๋ง๋ค ์ฟผ๋ฆฌ์ ์๋ ์บ์๋ ๋ฐ์ดํฐ๋ค์ด ๋ชจ์กฐ๋ฆฌ ์ฌ๋ผ์ง๋ ์ผ์ด ์์๋ค. ์์ํก ํ์ด์ง์์ ๋ฉ์์ง๋ค์ ์ ๋๋ก ํจ์นญ์ด ๋๋๋ฐ ์ฟผ๋ฆฌ ๋ฐ๋ธํด์๋ ์๋ณด์ด๋ ์ํฉ์ด์์. ์ ์์ด ๋นํฉํ๋ค. ๋คํํ ์คํ์ ๋๋ ๋น์ทํ ๊ฒฝํ์ด ์์๋ ์ฌ๋์ด ์๋ฌธ์๋ตํ๋ ๊ธ์ด ์์๋ค. queryClient๊ฐ App๋ด์ ์์ด์ ์ฌ๋ ๋๋ง์ด ๋๋๊ฒ ๋ฌธ์ ์๋ค.
function App() {
const { handleError } = useApiError();
const queryClient = useQueryClient();
queryClient.setDefaultOptions({
queries: { onError: (error: any) => handleError(error) },
mutations: {
onError: (error: any) => {
handleError(error);
},
},
});
// ...
์ฐ์ index.tsx์์๋ ์๋ฌด๋ฐ ์ต์
์์ด new QueryClient();
๋ก ์ฟผ๋ฆฌํด๋ผ์ด์ธํธ๋ฅผ ์์ฑํ๋ค. ๊ทธ ํ app.tsx์์ useQueryClient()
๋ก ์ธ์คํด์ค๋ฅผ ๊ฐ์ ธ์ ๋ํดํธ ์ต์
์ ์ง์ ํ ์ ์๋ค. onError ์ด๋ฒคํธ์ handleError๋ฅผ ์ ๋ฌํด์ค๋ค. ๋ปํ์ง ์์ ์ฝ์ง์ด์์ง๋ง ๋๋ฆ ์ป์ด๊ฐ๋๊ฒ ๋ง์๋ค.