[React] Tanstack-Query에 대해서 알아보자 & 무한스크롤 구현


최근에 인프런 멘토링을 받았는데 tanstack-query는 프론트엔드 개발자에게 선택이 아닌 필수다! 라는 이야기를 듣고 이번 프로젝트에 직접 사용해보려고 한다.
일단 사용하기에 앞서 공부한 내용을 정리하려고 한다 ! 🌱
(평소에는 공부한 내용들을 깃허브에 기록하지만 내가 작성한 내용이 누군가에게 도움이 되면 좋겠는 마음에 …. ✨)
✨ Tanstack-Query(react-query)가 뭔가요 ?
- Tanstack-Query는 서버로부터 데이터 가져오기 + 데이터 캐싱 + 캐시 제어 등 데이터를 쉽고 효율적으로 관리할 수 있는 라이브러리이다.
쉽게 설명하자면
- 서버 데이터 가져오기(fetching)
보통 웹앱에서 API 서버에 요청해서 데이터를 받아와야 하는데 (ex. 게시글 목록, 사용자 정보, 댓글)
TanStack Query를 사용하면 API 호출하는 코드를 간단하게 작성할 수 있다
내부적으로 데이터 요청 시 상태(로딩중, 성공, 실패)도 자동으로 관리해준다
- 데이터 캐싱(caching)
서버에서 받은 데이터는 자동으로 캐시에 저장된다
그래서 같은 데이터를 또 요청할 때, 이미 캐시에 있으면 서버에 다시 요청하지 않고 캐시 데이터를 바로 보여준다
덕분에 사용자 경험이 훨씬 빨라지고 서버 부담도 줄어서 완전 최고 !
⁉️ 여기서 캐싱이 뭐지
→ **캐싱(cache)**은 자주 쓰는 데이터를 미리 저장해 두었다가 빠르게 꺼내 쓰는 기술이다
실제 웹에서의 캐시
웹 앱에서는 데이터를 서버(API)에서 불러오는데 시간이 걸린다
예를 들어 게시판 글을 불러오면
네트워크 요청 → 서버 처리 → 응답 받기 → 렌더링
이 과정은 1초 이상 걸릴 수도 있음
하지만 TanStack-Query 같은 라이브러리가 그 데이터를 메모리에 저장(=캐싱) 해두면
같은 데이터를 다시 보여줄 때 서버에 다시 안 물어보고 즉시 보여줄 수 있고
사용자는 훨씬 빠르게 화면을 보게 되고 서버 부담도 줄어든다
- 캐시 자동 갱신
캐시된 데이터가 너무 오래되면 자동으로 서버에서 최신 데이터를 다시 받아와 갱신한다
사용자가 뭔가 조작해서 데이터가 바뀌면 자동으로 최신 상태를 유지하도록 업데이트도 해준다
- 데이터 상태 관리 간소화
로딩중, 에러, 성공 상태를 별도로
useState
,useEffect
로 관리할 필요가 거의 없다TanStack Query가 알아서 상태 관리해주니까 개발자가 집중할 부분이 줄어든다
- 캐시 제어 및 동기화
개발자가 필요하면 언제든 캐시를 수동으로 갱신하거나 삭제할 수 있다
여러 컴포넌트에서 같은 데이터를 공유할 때 자동으로 동기화시켜준다
Tanstack-Query에 중요한 개념
staleTime(default:0)
데이터를 신선하다고 간주하는 시간이다.
이 시간이 지나기 전까지는 React Query는 데이터를 **“신선함”**으로 간주하고 자동으로 재요청을 하지 않는다
예를 들어
staleTime: 5000
으로 설정하면 데이터를 가져온 후 5초 동안은 재요청 없이 기존 데이터를 사용한다
useQuery(['todos'], fetchTodos, {
staleTime: 5000, // 5초 동안은 데이터를 fresh하게 유지
});
cacheTime (기본값: 5분)
쿼리가 언마운트된(=컴포넌트가 화면에서 사라지는 것) 후에도 메모리에 데이터를 유지하는 시간이다.
이 시간이 지나면 쿼리 데이터는 가비지 컬렉션처럼 삭제된다
예를 들어
cacheTime: 10000
이면, 컴포넌트가 언마운트된 후에도 쿼리 데이터는 10초간 캐시에 유지된다. 그 안에 같은 쿼리가 다시 마운트되면, 기존 데이터로 빠르게 로드된다.
useQuery(['todos'], fetchTodos, {
cacheTime: 10000, // 쿼리 언마운트 후 10초 동안 캐시 유지
});
구분되어야 하는 점
Zustand
와 같은 상태관리 라이브러리는 클라이언트의 데이터를 관리하는 것이고 Tanstack-Query
의 경우에는 서버 데이터를 관리하는 것이라서 사용목적이 다르다✍🏻
사용예시
Zustand
✅ 다크모드 on/off |
Tanstack-Query
✅ 게시글 목록 가져오기 |
📚 대표적으로 자주 사용되는 기능들
기술/개념 | 설명 |
useQuery | 데이터를 불러올 때 사용하는 훅. 서버에서 데이터 조회 (GET). |
useMutation | 데이터를 변경(쓰기) 할 때 사용. POST, PUT, PATCH, DELETE 등에 사용. |
queryKey | 쿼리의 고유한 키. 캐싱, 리패치 기준이 되는 중요한 요소. 예: ['posts', postId] |
queryFn | 데이터를 가져오는 함수. 보통 axios , fetch 등으로 API 요청을 보냄. |
staleTime | 데이터를 fresh로 간주하는 시간. 이 시간 내엔 자동 리패치 안 함. |
cacheTime | 컴포넌트가 언마운트된 뒤 캐시를 메모리에 유지하는 시간. |
refetch | 데이터를 수동으로 다시 불러오는 함수. const { refetch } = useQuery(...) |
onSuccess , onError | 쿼리나 뮤테이션이 성공 또는 실패했을 때 실행할 콜백 함수. |
enabled | 조건이 true일 때만 쿼리를 실행. 조건부 쿼리에 사용됨. |
select | 쿼리 결과를 가공해서 리턴하는 함수. 데이터를 정제하거나 일부만 추출할 때 유용. |
invalidateQueries | 특정 쿼리를 무효화하여 다시 refetch하게 만드는 기능. (queryClient.invalidateQueries ) |
setQueryData | 쿼리 캐시의 데이터를 직접 업데이트. optimistic UI 구현 등에 사용. |
실제 사용 예시
이번 프로젝트에서 무한스크롤을 Tanstack-Query
를 사용해서 구현해봤다.
Tanstack-Query
는 useInfiniteQuery
, getPreviousPageParam
, fetchPreviousPage
, hasPreviousPage
등과 같은 페이지 관련 기능이 존재해 이를 이용해 무한 스크롤을 쉽게 구현할 수 있었다..!!
import { useInfiniteQuery } from '@tanstack/react-query'
import InfiniteScrollList from '../../../../components/common/InfiniteScrollList'
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = useInfiniteQuery({
queryKey: ['relatedPosts', currentId], //쿼리 고유 키
queryFn: async ({ pageParam = null }) => {
const { boards } = await getAllPosts({ last: pageParam, size: 6 }) //API 요청
return boards.filter((p) => String(p.boardId) !== String(currentId)) //현재 게시글 제외
},
getNextPageParam: (lastPage) => {
const lastItem = lastPage[lastPage.length - 1]
return lastItem?.boardId ?? undefined
},
enabled: !!currentId,
})
const posts = data?.pages.flat() || []
//data.pages는 2차원 배열이므로 flat()으로 하나의 배열로 합침
return (
<InfiniteScrollList
onIntersect={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
</InfiniteScrollList>
)
}
export default RelatedPosts
useInfiniteQuery
: React Query의 훅. 무한 스크롤용 쿼리를 설정한다queryKey
: 쿼리를 구별하는 고유 키 (캐싱에 사용됨)queryFn
: 서버에서 데이터를 가져오는 함수 (getAllPosts
)getNextPageParam
: 다음 페이지 요청에 필요한 값을 설정 (여기선 마지막 게시글의boardId
)enabled
: 조건이 true일 때만 쿼리를 실행 (여기선currentId
가 존재할 때)
이런식으로 사용해줬다.
지금 구인 쪽은 fresh 상태로 뜨고 있는 것을 확인할 수 있다..✨
🌱 배운점
리액트 쿼리에 대한 궁금증은 가득했지만 실제로 적용해보고 공부해본 적이 이번이 처음인데, 굉장히 유용한 기능이다 🥳
특히 서버에서 데이터를 가져오고 관리하는 과정이 자동화되어 불필요한 로딩 상태 관리나 캐시 처리 코드가 줄어든다는 점이 인상적이었다.무한 스크롤 구현도
useInfiniteQuery
하나로 깔끔하게 해결할 수 있었고queryKey
를 통해 데이터를 구분하고 캐싱하는 방식도 굉장히 체계적이라 느꼈다. 처음에는 다소 어렵게 느껴졌지만 개념 하나하나를 이해하고 나니 React Query는 서버 데이터 관리의 필수 도구라는 확신이 생겼다.앞으로도 더 복잡한 기능(
invalidateQueries
,onSuccess
,refetch
) 등을 직접 활용하면서 계속 익혀보고 싶다! 💪
Subscribe to my newsletter
Read articles from 송수빈 directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
