Search

[Pattern] 선언적 데이터 페칭 패턴

1. 세션 요약 - 빈칸 채우기

(기본 원칙) 데이터 페칭에서 _______을 선언하고 _______는 라이브러리가 처리하게 하여, _______과 _______의 복잡성은 줄이고 _______ 로직에만 집중해야 한다.
(선언적 데이터 페칭 전문가 5단계)
1.
사전 세팅에서 _______와 _______, _______을 설정한다.
2.
_______를 사용하여 재사용 가능한 쿼리 정의를 만든다.
3.
컴포넌트에서 _______를 _______와 조합하여 데이터를 소비한다. 이 경우 추후 _______ _______ 교체 시 조합되는 부분만 변경하면 된다.
4.
여러 쿼리는 _______에 _______ 배열로 전달하여 병렬 처리한다.
5.
_______, _______, _______에서 동일한 쿼리 옵션을 재사용한다.

2. 세션 목표

개발을 하다보면 매번 동일한 로딩 / 에러 상태 관리 코드를 반복해서 작성하게 됩니다.
데이터를 사용하는 곳마다 if (loading) return <Skeleton /> 혹은 옵셔널 체이닝 등의 분기 처리가 반복됩니다.
useState, useEffect로 data, loading, error를 각각 관리하면서 코드 읽기의 복잡성 때문에 인지 부하가 심해집니다.
점점 코드의 흐름을 따라가기 어려워지고, 상태 관리 과정에서 실수가 발생하여 버그가 발생합니다.
이번 세션에서 다루는 내용을 잘 이해하면,
로딩 / 에러 상태 관리 코드를 자신감 있게 유지보수 가능하게 작성할 수 있게 됩니다.
반복되는 분기 처리를 제거하여 컴포넌트의 핵심 로직만 남길 수 있게 됩니다.
Parse Don't Validate 철학처럼, 로딩 상태를 경계에서 한 번만 처리하여 인지 부하를 크게 줄일 수 있게 됩니다.

3. 인접 개념

Stale-While-Revalidate
캐시된 데이터를 먼저 보여주고 (stale) & 백그라운드에서 새 데이터 가져오기 (revalidate)
Parse Don't Validate
Parse Don't Validate: 매번 확인하지 말고, 경계에서 한 번만 처리하라
→ 로딩 상태에 적용하면: "매번 로딩 상태를 확인하지 말고, Suspense 경계에서 한 번만 처리하라"
예시 코드
서버 상태 vs 클라이언트 상태
서버 상태: 원본이 서버에 있는 데이터, 여러 클라이언트가 공유, 비동기적으로 변경될 수 있음 (ex. 사용자 정보, 게시글 목록)
클라이언트 상태: 클라이언트에서만 관리하는 데이터, 동기적으로 제어 가능 (ex. 모달 열림 / 닫힘, Form 입력값)

4. 멘탈 모델 & 시각화 자료

// 🚫 명령형: HOW를 상세히 기술 const [data, setData] = useState(); const [loading, setLoading] = useState(false); const [error, setError] = useState(); useEffect(() => { setLoading(true); fetch("/api/data") .then((res) => res.json()) .then(setData) .catch(setError) .finally(() => setLoading(false)); }, []); // ✅ 선언형: WHAT만 선언 const { data, isLoading, error } = useQuery({ queryKey: ["user"], queryFn: fetchUser, }); const { data, isLoading, error } = useQuery(userQueryOption());
TypeScript
복사
@tanstack/react-query 의 데이터 페칭 & 캐싱 flow

5. '선언적 데이터 페칭' 패턴이란?

데이터가 필요한 "의도"만 선언하고, "어떻게" 가져올지는 라이브러리에게 위임하는 패턴
개발자는 비즈니스 로직에 집중하고, 데이터 페칭의 복잡한 세부사항(캐싱, 에러 처리, 재시도, 동기화 등)은 추상화된 레이어가 처리합니다.

핵심 특징

1.
선언적 인터페이스: 필요한 데이터와 조건만 명시하고 구현 세부사항은 숨김
2.
자동 상태 관리
loading, error, success 상태 자동 처리
요청 실패 시 3회 자동 재시도 및 지수 백오프(Exponential Backoff) 지원
3.
스마트 캐싱
중복 요청 자동 제거
백그라운드 리페칭

6. 예제 풀이

1. 다음 코드를 주어진 단어들로 분류해보세요:
2. React Query 패턴의 Use Case를 보고, 각 패턴을 사용하는 이유와 장단점을 설명해보세요

7. 전문가들의 '선언적 데이터 페칭' 패턴

7-1. 미니 과제 - Before

7-2. 전문가 영상

1.
데이터 페칭을 하기 위해 필요한 fetcher를 만들고, 필요한 타입을 추가한다.
데이터 페칭을 위한 가장 기본적인 계층과 단위부터 차곡차곡 쌓아올린다. 각각의 단위가 독립적으로 사용되고 사용처에서 유연하게 조합될 수 있도록 결합은 최대한 미룬다.
1) http 클라이언트 → 2) fetcher → 3) 응답 / 요청 인터페이스 추가 → 4) 쿼리 옵션 → 5) 쿼리 훅 → …
오직 데이터 페칭만을 목적으로 하는 useGetCategories 같은 불필요한 wrapper 훅을 만드는 것을 지양한다. 그보다는 사용처에서 useSuspenseQuery(categoryOptions()) 형태로 조합해서 사용한다.
모든 이름을 하나하나 고민하지 않는다. 패턴에 따라 기계적으로 네이밍 한다. → [Tip] 서버 / 클라이언트 간 인터페이스 작성
예시 코드: fetcher, 타입 추가
2.
데이터를 어디서 받아오고, 어떻게 내려줄 것인지 결정한다.
데이터 호출의 책임이 어디에 있나? (페이지? 컴포넌트?)
데이터의 생명 주기가 어떻게 되는가? (업데이트 될 여지가 있나? 혹은 한번 받아오고 바뀔 일이 없나?)
데이터를 받아온 뒤 소비처에 어떻게 전달할 것인가?
데이터 호출 → 가공 → 렌더링의 레이어를 어떻게 나눌 것인가?
3.
데이터 페칭이 필요한 컴포넌트에 쿼리 훅을 추가하고, fetcher를 연결한다.
받아온 데이터에 대한 로딩 상태가 전파되는 걸 막기 위해 useSuspenseQuery + Suspense를 1옵션으로 고려한다.
주의 사항
Suspense를 사용할 경우 데이터 페칭이 UI 렌더링과 엮이므로, ErrorBoundary 등의 처리를 적절히 해주지 못할 경우 에러 시 화면이 렌더링되지 않을 수 있다.
useSuspenseQuery를 사용할 때는 상위 컴포넌트에서 Suspense로 감싸주어야 제대로 적용된다. 같은 컴포넌트 내에서 사용하면 적용되지 않는다.
예시 코드: useQuery / useSuspenseQuery
4.
데이터 호출을 확인하고 쿼리 훅에 넘겨지는 값들을, 쿼리 옵션으로 분리한다.
쿼리 훅에 넘겨지는 쿼리 키, fetcher 등 항상 같이 움직이는 값들을 쿼리 옵션으로 묶어두면 응집도와 재사용성이 높아진다.
프로젝트 규모나 성격에 따라 ‘타입 / fetcher / 쿼리 옵션’을 한 파일로 묶거나, 별도의 폴더로 분리하기도 한다.
ex. remotes/consumptions/fetcher.ts, remotes/consumptions/queryOptions.ts
ex. remotes/consupmtions.ts, queryOptions/consumptions.ts
예시 코드: queryOptions로 재사용 가능한 쿼리 정의 & 쿼리 옵션 재활용
5.
만약 한 컴포넌트 내에서 useSuspenseQuery가 여럿 사용될 경우, useSuspenseQueries에서 쿼리 옵션을 조합하는 형태로 리팩토링 한다.
useSuspenseQuery가 한 컴포넌트에서 여러 개 존재할 경우 각 훅의 호출이 순차적으로 이루어져 Network Waterfall 현상을 일으킬 수 있으므로 병렬로 호출해야 한다.
예시 코드: Suspense 케이스에서의 쿼리 병렬 요청 최적화

7-3. 미니 과제 - After

8. Wrap-up

세션 핵심 문장 완성!

이제 도입부의 빈칸을 채워보세요!
(기본 원칙) 데이터 페칭에서 무엇(What)을 선언하고 어떻게(How)는 라이브러리가 처리하게 하여, 로딩에러의 복잡성은 줄이고 비즈니스 로직에만 집중해야 한다.
(선언적 데이터 페칭 전문가 6단계)
1.
사전 세팅에서 HTTP clientfetcher 함수, 요청 / 응답 타입을 설정한다.
2.
queryOptions를 사용하여 재사용 가능한 쿼리 정의를 만든다.
3.
컴포넌트에서 queryOptionsuseQuery와 조합하여 데이터를 소비한다. useQuery useSuspenseQuery 교체 시 조합되는 부분만 변경하면 된다.
4.
여러 쿼리는 useSuspenseQueriesqueries 배열로 전달하여 병렬 처리한다.
5.
prefetch, 캐시 조작, 컴포넌트에서 동일한 쿼리 옵션을 재사용한다.