From 89c7930fc6f2fdd07b6b667091afb6facb90288b Mon Sep 17 00:00:00 2001 From: tjd985 Date: Tue, 6 May 2025 17:48:07 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[Docs]=207.2=EC=9E=A5=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../seongho.md" | 103 +++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git "a/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.2_API_\354\203\201\355\203\234_\352\264\200\353\246\254\355\225\230\352\270\260/seongho.md" "b/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.2_API_\354\203\201\355\203\234_\352\264\200\353\246\254\355\225\230\352\270\260/seongho.md" index 3beced8..fb903e8 100644 --- "a/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.2_API_\354\203\201\355\203\234_\352\264\200\353\246\254\355\225\230\352\270\260/seongho.md" +++ "b/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.2_API_\354\203\201\355\203\234_\352\264\200\353\246\254\355\225\230\352\270\260/seongho.md" @@ -1 +1,102 @@ - +### 상태 관리 라이브러리에서 호출하기 +--- +실제 API를 호출하는 코드는 컴포넌트 내에서 비동기 함수를 직접 호출하지 않음.
+비동기 API를 호출하기 위해서는 API의 성공 및 실패에 따른 상태관리가 되어야 하므로, 상태관리 라이브러리의 액션이나 훅과 같이 재정의된 형태를 사용해야 함 + +상태 관리 라이브러리의 비동기 함수들은 서비스 코드를 사용해서 비동기 상태를 변화 시킬 수 있는 함수를 제공함.
+컴포넌트는 이러한 함수를 사용해서 상태를 구독하며, 상태가 변경될 때 컴포넌트를 다시 렌더링하는 방식으로 동작함. + +`Redux`예시를 살펴보자 +```ts +import { useEffect } from "react"; +import { useDispatch, useSelector } from 'react-redux'; + +export function useMonitoringHistory() { + const dispatch = useDispatch(); + + const searchState = useSelector((state) => state.monitoringHistory.searchState); + + const getHistoryList = async ( + newState: Partial + ) => { + const newSearchState = { ...searchState, ...newState }; + dispatch(monitoringHistorySlice.actions.changeSearchState(newSearchState)); + + const response = await getHistories(newSearchState); // 비동기 API 호출 + dispatch(monitoringHistorySlice.actions.fetchDate(response)); + } + + return { + searchState, + getHistoryList + }; +} +``` +해당 코드는 `getHistoryList`만 호출하고, 해당 결과를 받아와서 상태를 업데이트 하는 일반적인 방식으로 사용할 수 있음. 그러나, 해당 코드에서는 `getHistoryList`함수에서는 `dispatch`코드를 제외하더라도 **API호출**과 **상태 관리 코드**를 작성해야 함. + +```ts +const API = axios.create(); + +const setAxiosInterceptor = (store: EnhancedStore) => { + API.interceptors.request.use( + (config: AxiosRequestConfig) => { + const { params, url, method } = config; + + // 전역상태 관리 코드... + + return config; + }, + (error) => Promise.reject(error) + ); + + API.interceptors.response.use( + (response: AxiosResponse) => { + const { method, url } = response.config; + + // 전역상태 관리 코드... + + return response.data.data; + }, + (error) => { + // 전역상태 관리 코드... + + return Promise.reject(error); + } + ); +} +``` +요런식으로 API를 호출할 때, 호출한 뒤, 에러가 발생했을 때 각각 전역상태를 세팅해주어야 함. + +
+ +### 훅으로 호출하기 +--- +`react-query`나 `swr`과 같은 훅을 사용한 방법은 전역 상태 관리 라이브러리를 사용한 방식보다 훨씬 간단함.
+이러한 훅은 캐시를 사용하여 비동기 함수를 호출하며, 의도치 않은 상태 변경을 방지하는데 도움이 됨. + +```ts +// 커스텀 훅 +const useGetJobList = () => { + return useQuery(['getJobList'], async () => { + const response = await JobService.fetchJobList(); + + // View Model을 사용해서 결과 return + return new JobList(response); + }); +} +``` +이렇게 작성한 이후, 일반적인 훅을 호출하는 것 처럼 사용하면 됨.
+만약, 항시 최신 상태를 표현하려면 `폴링`이나 `웹소켓` 등의 방식을 사용해야 함. + +> [!TIP] +> **폴링(Polling)**
+> 클라이언트가 주기적으로 서버에 요청을 보내 데이터를 일정 주기마다 최신 데아터로 업데이트 하는 방식 + +
+ +전역 상태 관리 라이브러리에서는 비동기로 상태를 변경하는 코드가 추가되면 점점 전역 상태 관리 스토어가 비대해지는 것을 볼 수 있음.
+따라서 `redux`나 `mobX`와 같은 라이브러리를 `react-query`로 변경하는 추세임. + +그러나 늘 그렇듯이 `react-query`가 정답인것은 아님. 어떤 상태 관리 라이브러리를 선택할지는 상황에 따라 적절한 판단이 필요함. + + From a75cbb4d27e46dc9a777b4d21ffba6d383d9ffca Mon Sep 17 00:00:00 2001 From: tjd985 Date: Tue, 6 May 2025 19:12:09 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[Style]=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=A4=84=EB=B0=94=EA=BF=88=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../seongho.md" | 2 -- 1 file changed, 2 deletions(-) diff --git "a/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.2_API_\354\203\201\355\203\234_\352\264\200\353\246\254\355\225\230\352\270\260/seongho.md" "b/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.2_API_\354\203\201\355\203\234_\352\264\200\353\246\254\355\225\230\352\270\260/seongho.md" index fb903e8..3c5068e 100644 --- "a/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.2_API_\354\203\201\355\203\234_\352\264\200\353\246\254\355\225\230\352\270\260/seongho.md" +++ "b/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.2_API_\354\203\201\355\203\234_\352\264\200\353\246\254\355\225\230\352\270\260/seongho.md" @@ -98,5 +98,3 @@ const useGetJobList = () => { 따라서 `redux`나 `mobX`와 같은 라이브러리를 `react-query`로 변경하는 추세임. 그러나 늘 그렇듯이 `react-query`가 정답인것은 아님. 어떤 상태 관리 라이브러리를 선택할지는 상황에 따라 적절한 판단이 필요함. - - From 92e440fc6dc3e57fb12523fd179da5099802867e Mon Sep 17 00:00:00 2001 From: tjd985 Date: Tue, 6 May 2025 19:12:17 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[Docs]=207.3=EC=9E=A5=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../seongho.md" | 176 +++++++++++++++++- 1 file changed, 175 insertions(+), 1 deletion(-) diff --git "a/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.3_API_\354\227\220\353\237\254_\355\225\270\353\223\244\353\247\201/seongho.md" "b/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.3_API_\354\227\220\353\237\254_\355\225\270\353\223\244\353\247\201/seongho.md" index 3beced8..f4662e1 100644 --- "a/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.3_API_\354\227\220\353\237\254_\355\225\270\353\223\244\353\247\201/seongho.md" +++ "b/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.3_API_\354\227\220\353\237\254_\355\225\270\353\223\244\353\247\201/seongho.md" @@ -1 +1,175 @@ - +비동기 API호출을 하다보면, 상태 코드에 따라서 다양한 에러를 마주하게 됨.
+ts에서 어떻게 에러를 처리하고, 명시할 수 있는지 알아보자 + +
+ +### 타입 가드 활용하기 +--- +`Axios`를 사용한다면 `isAxiosError`라는 타입가드를 활용할 수 있지만, 서버 에러임을 명확하게 표시하고, 서버에서 내려주는 에러 응답 객체에 대해서도 구체적으로 정의하면 더 좋음.
+ +```ts +interface ErrorResponse { + status: string; + serverDataTime: string; + errorCode: string; + errorMessage: string; +} + +function isAxiosError(error: unknonw): error is AxiosError { + return axios.isAxiosError(error); +} +``` +이렇게 작성하면 서버 에러인지, 클라이언트 에러인지 명확하게 구분이 가능함. + +```ts +try { + // API 호출 로직 +} catch(error) { + if (isAxiosError(error)) { + // 서버 에러 + setErrorMessage(error.errorMessage); + + return; + } + + // 클라이언트 에러 + setErrorMessage('클라이언트 에러'); +} +``` + +
+ +### 에러 서브클래싱 하기 +--- +> [!NOTE] +> **서브클래싱(Subclassing)**
+> 기존 클래스를 확장하여 새로운 클래스를 만드는 과정을 말함.
+> 새로운 클래스는 상위 클래스의 모든 속성과 메서드를 상속받아서 사용할 수 있고, 추가적으로 새로운 속성과 메서드를 정의할 수도 있음. + +우리가 코드를 작성하다보면 다양한 에러가 내려올 때가 있는데 이때 서브클래싱을 활용하면 어떤 에러인지 바로 확인할 수 있고, 다르게 처리가 가능함. + +```ts +class OrderHttpError extends Error { + private readonly privateResponse: AxiosResponse | undefined; + + constructor(message: string, response?: AxiosResponse) { + super(message); + + this.name = 'OrderHttpError'; + this.privateResponse = response; + } + + getResponse(): AxiosResponse | undefined { + return this.privateResponse; + } +} + +class NetworkError extends Error { + constructor(message = '') { + super(message); + + this.name = 'NetworkError'; + } +} + +class UnauthorizedError extends Error { + constructor(message = '') { + super(message); + + this.name = 'UnauthorizedError'; + } +} +``` + +요런식으로 케이스를 분류해둔 뒤, 가공해서 처리할 수 있음 + +```ts +const enum HttpStatusCode { + 'NETWORK' = 500, + 'UNAUTHORIZED' = 401 +} + +// 에러 분류 +const classifyErrorByType = ( + error: Error | AxiosError +) => { + if (isAxiosError(error)) { + const { response } = error; + + switch (response) { + case response === HttpStatusCode.NETWORK : + return Promise.reject( + new NetworkError(response.data.message) + ); + + case response === HttpStatusCode.UNAUTHORIZED : + return Promise.reject( + new UnauthorizedError(response.data.message) + ); + + default: + return Promise.reject( + new OrderHttpError( + response.data.message, + response + ) + ); + } + } else { + return Promise.reject(error); + } +} + +// 에러별 핸들링 +const handleError = (error: unknown) { + if (error instanceof UnauthorizedError) { + return onUnauthorizedError( + error.message, + // ... + ); + } + + if (error instanceof NetworkError) { + return alert('네트워크 연결이 원활하지 않습니다.'); + } + + return onOrderHttpError( + error.message, + error + ); +} +``` +이렇게 해두면 아래와 같이 활용할 수 있음. + +```ts +const getJobList = async () => { + try { + // API 호출 로직 + } catch (error) { + const classifiedError = classifyErrorByType(error); // 에러 분류 + handleError(classifiedError); // 분류된 에러 별 액션 + } +} +``` + +
+ +### 인터셉터를 활용한 에러 처리 +--- +만약 `Axios`를 사용한다면 이러한 에러 분류를 `interceptor`에 붙히는 것도 좋은 방법인 것 같음. + +```ts +axios.interceptors.response.use( + (response: AxiosResponse) => response, + classifyErrorByType, // 인터셉터에서 에러 분류 +); +``` + +
+ +### 리액트 쿼리를 활용한 에러 처리 +--- +리액트 쿼리에서는 `onError`나 `isError`와 같이 에러 핸들러나, 상태를 반환해주는 플래그가 존재하기 때문에 훨씬 에러를 관리하기가 쉬움. + +> [!CAUTION] +> ***`onError는` react-query의 4버전까지만 존재하는 에러 핸들러임 (5버전에서는 삭제)***
From 98885ab300e3518a91694db808e1be2be4ef6d99 Mon Sep 17 00:00:00 2001 From: tjd985 Date: Tue, 6 May 2025 19:30:13 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[Docs]=207.4=EC=9E=A5=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../seongho.md" | 75 ++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git "a/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.4_API_\353\252\250\355\202\271/seongho.md" "b/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.4_API_\353\252\250\355\202\271/seongho.md" index 3beced8..5df2bf8 100644 --- "a/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.4_API_\353\252\250\355\202\271/seongho.md" +++ "b/CH07_\353\271\204\353\217\231\352\270\260_\355\230\270\354\266\234/7.4_API_\353\252\250\355\202\271/seongho.md" @@ -1 +1,74 @@ - +FE개발을 하다보면 API가 나오기 전에 개발을 진행해야 하는 일이 종종 생김.
+그렇다면 이러한 상황에서는 프론트엔드 개발을 어떻게 진행할 수 있을까?? + +
    +
  1. + 임시 더미 데이터를 만들어서 구현하기 +
  2. + + -> 요청 응답에 따라서 각기 다른 화면을 보여줘야 할 경우 대응하기가 힘들다. + +
  3. + 별도의 가짜 서버를 만들어서 운영하기 +
  4. + + -> 프론트 개발 과정에서 발생하는 모든 예외를 처리하는 것은 쉽지 않다. + +
+ +
+ +이럴 때 `모킹`이라는 방법을 활용할 수 있음.
+`모킹`을 활용하면 앞서 제시한 상황에서 유연하게 대처할 수 있음. 또한 서버의 영향을 받지 않고 프론트엔드 개발을 할 수 있게 됨.
+ +그렇다면 모킹에는 어떤 방법들이 있을까? + +
+ +### JSON파일 불러오기 +간단한 조회만 필요한 경우에는 .json파일을 만들거나, js파일안에 JSON형식의 정보를 저장하고 export해주는 방식을 사용하면 됨.
+이러고 get요청에 해당 파일 경로를 삽입해주면 조회 응답으로 원하는 값을 받을 수 있음. +```ts +// mock/service.ts +const MOCK = [ + { + name: 'OSH', + age: 27, + } +]; + +export default MOCK; + +// api +const getPerson = apiRequester.get('/mock/service.ts'); +``` + +
+ +### NextApiHandler 활용하기 +--- +만약 `NextJS`를 사용하고 있다면, NextApiHandler를 사용하는 방법도 있음. +```ts +// route.ts +const MOCK = [ + // mock data... +] + +export async function GET(req: NextRequest) { + return NextResponse.json(MOCK); +}; +``` + +
+ +### axios-mock-adapter활용하기 +--- +만약, axios를 사용한다면 axios-mock-adaptor를 사용할 수 있음. +> [!WARNING] +> `axios-mock-adapter`는 api요청을 중간에 가로채는 것 이기 때문에 실제 API 요청을 주고받지는 않음. + +
+ +### 그 외 방법들 +--- +나는 개인적으로도 사용해본 `msw`나 `mirage`도 괜찮다고 생각함. From 97a6628cc87aa8e1495453b4104ab57023750624 Mon Sep 17 00:00:00 2001 From: tjd985 Date: Sun, 11 May 2025 15:13:46 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[Docs]=207=EC=A3=BC=EC=B0=A8=20=ED=95=99?= =?UTF-8?q?=EC=8A=B5=EC=9D=BC=20=EB=B0=8F=20=EC=99=84=EB=A3=8C=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4602e8d..b77f96a 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ | 4주차 | 4.2 타입 좁히기 - 타입 가드 ~ 4.4 Exhaustiveness Checking으로 정확한 타입 분기 유지하기 | 2025.04.01 | ✅ | | 5주차 | 5장 타입 활용하기 | 2025.04.15 | ✅ | | 6주차 | 6.1 자바스크립트의 런타임과 타입스크립트의 컴파일 ~ 7.1 API 요청 | 2025.04.22 | ✅ | -| 7주차 | 7.2 API 상태 관리하기 ~ 7.4 API 모킹 | YYYY.MM.DD | | +| 7주차 | 7.2 API 상태 관리하기 ~ 7.4 API 모킹 | 2025.05.06 | ✅ | | 8주차 | 8장 JSX에서 TSX로 | YYYY.MM.DD | | | 9주차 | 9장 훅 ~ 10장 상태관리 | YYYY.MM.DD | | | 10주차 | 11장 CSS-in-JS ~ 12장 타입스크립트 프로젝트 관리 | YYYY.MM.DD | |