-
Notifications
You must be signed in to change notification settings - Fork 0
State & Data Fetching
이 문서는 세모(Delta-front) Web의 상태 관리와 데이터 패칭 구조를 정리합니다.
“UI 상태는 로컬로”, “서버 상태는 TanStack Query로”를 기본 원칙으로 합니다.
탭 선택, 모달 열림, 입력값 등 화면 단위 상태는 로컬 상태로 관리합니다.
-
useState,useReducer,useMemo등 - 페이지(
src/app/**) 또는 컴포넌트(src/shared/components/**) 내부에서 관리
서버에서 받아오는 데이터는 캐싱/동기화/리패치가 필요하므로 TanStack Query로 관리합니다.
-
useQuery/useMutation - 캐시 키(QueryKey) 기반 데이터 일관성 유지
src/shared/apis는 도메인 단위로 아래 구조를 유지합니다.
shared/apis
├─ api.ts # axios instance + interceptors(토큰/재발급/에러)
├─ api-types.ts # ApiResponse / ApiResponseError / unwrap
├─ api-error.ts # ApiError class
├─ error-codes.ts # 서버 에러 코드 상수
├─ token-storage.ts # access/refresh token 저장/조회/삭제
├─ constants
│ ├─ api-paths.ts # API_PATHS (endpoint 단일 소스)
│ └─ api-headers.ts # API_HEADERS (헤더 키 단일 소스)
├─ auth
│ ├─ auth-api.ts # auth 관련 API 호출 함수
│ ├─ auth-keys.ts # auth QueryKey factory
│ ├─ auth-events.ts # emitAuthLogout 등 이벤트
│ ├─ kakao-oauth.ts # 카카오 OAuth 유틸(state/redirectUri 등)
│ └─ hooks/ # useKakaoLoginMutation 등
├─ user
│ ├─ user-api.ts
│ ├─ user-keys.ts
│ └─ hooks/
└─ storage
├─ storage-api.ts
├─ storage-keys.ts
└─ hooks/
규칙: “API 호출 함수는
*-api.ts”, “QueryKey는*-keys.ts”, “훅은hooks/”로 고정합니다.
shared/apis/api.ts에서 axios instance를 단일로 운영합니다.
-
tokenStorage.getTokens()로 access token을 읽고 -
Authorization헤더에Bearer <token>을 자동으로 붙입니다. - 이미 헤더가 존재하면 중복 주입하지 않습니다.
효과
- 각 API 함수에서 토큰을 일일이 넣지 않아도 됨
- “인증 헤더 정책”을 한 곳에서 통제 가능
- 응답 헤더에서
-
Authorization(access) -
Refresh-Token(프로젝트에서 정의한 헤더) 를 읽어tokenStorage.setTokens()로 갱신합니다.
-
효과
- 서버가 토큰을 재발급/갱신해도 클라이언트가 자동 반영
서버 에러 응답이 ApiResponseError 형태인지 검사(isApiResponseError) 후,
-
ApiError.fromPayload(payload, traceId)로 표준 에러 객체로 변환합니다.
-
TOKEN_REQUIRED(토큰 없음/만료 등) → 즉시 로그아웃 처리tokenStorage.clear()emitAuthLogout()
-
AUTHENTICATION_FAILED(access 만료/검증 실패) → refresh로 재발급 후 1회 재시도-
_retry플래그로 무한 루프 방지 -
runReissueOnce()는 동시성 제어(reissuePromise 공유)로 중복 재발급 방지 - 재발급 성공 후 원래 요청을 재실행(
instance(config))
-
config._skipAuthRefresh또는 reissue 자체 요청(API_PATHS.AUTH.REISSUE)은 재발급 로직에서 제외합니다.
효과
- “access 만료”는 사용자 경험을 끊지 않고 자동 복구
- “refresh 없음/불일치”는 보안적으로 즉시 종료(로그아웃)
서버 응답은 다음을 기본으로 합니다.
- 성공:
ApiResponse<T> = { status, code, data, message } - 실패:
ApiResponseError = { status, code, data: null, message }
unwrapApiResponse(res)로 data만 꺼내는 유틸을 사용합니다.
효과
- 각 API에서 반환 형태가 단순해져서 Query 훅/컴포넌트 사용이 쉬워짐
각 도메인별 *-keys.ts는 QueryKey 생성을 담당합니다.
예시(권장 형태):
authKeys.me()userKeys.me()storageKeys.presignedGet(params)
효과
- invalidate/refetch 시 키를 문자열로 하드코딩하지 않음
- 키 구조가 일관되어 캐시 관리가 쉬움
- 이름:
use + 행위 + 대상 + Query - 예:
useGetMyProfileQuery
- 이름:
use + 행위 + 대상 + Mutation - 예:
useKakaoLoginMutation
- API 호출 함수(
*-api.ts)를 사용해 데이터 패칭 - 필요한 경우
select로 데이터 가공(unwrapApiResponse등) - 성공/실패 후 invalidate 등 캐시 제어
공통 컴포넌트는 원칙적으로 훅을 직접 호출하기보다, 페이지에서 데이터를 가져와 props로 주입하는 방식을 우선합니다.
emitAuthLogout()은 “토큰 만료/인증 불능” 상황을 앱 전역에 알립니다.
권장 흐름
- 전역(또는 페이지)에서 logout 이벤트를 구독
- 로그인 페이지로 이동 / 토스트 표시 / 캐시 정리 등 수행
이 방식으로 API 레이어가 라우터를 직접 의존하지 않고도, “로그아웃 이후 UI 처리”를 분리할 수 있습니다.
향후 apps/app에서 RN WebView로 apps/web을 감쌀 예정이므로,
-
sessionStorage/localStorage사용은 “환경에 따라 실패 가능”을 고려합니다. - 토큰 전략이 WebView에서 달라질 수 있으므로, 필요한 시점에
- 네이티브 브릿지(
postMessage) 기반 토큰 주입 - secure storage 연동 같은 방식으로 확장합니다.
- 네이티브 브릿지(
-
API_PATHS,API_HEADERS는 단일 소스로만 관리한다 - 모든 에러는
ApiError로 정규화되어 호출부가 처리한다 - 401 재발급은
runReissueOnce()로 동시성 제어한다 - QueryKey는 Factory로 만들고 하드코딩하지 않는다
- UI 상태는 로컬, 서버 상태는 TanStack Query로 분리한다
- 🏠 Home
- 🔐 Environment
- 🧰 Tech Stack
