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

최근에 인프런 멘토링을 받았는데 **tanstack-query는 프론트엔드 개발자에게 선택이 아닌 필수**다! 라는 이야기를 듣고 이번 프로젝트에 직접 사용해보려고 한다.

일단 사용하기에 앞서 공부한 내용을 정리하려고 한다 ! 🌱

(평소에는 공부한 내용들을 깃허브에 기록하지만 내가 작성한 내용이 누군가에게 도움이 되면 좋겠는 마음에 …. ✨)

[굉장히 많은 도움을 받은 블로그입니다,,👍🏻](https://velog.io/@kandy1002/React-Query-%ED%91%B9-%EC%B0%8D%EC%96%B4%EB%A8%B9%EA%B8%B0)

## ✨ T**anstack-Query(react-query)가 뭔가요 ?**

* Tanstack-Query는 **서버로부터 데이터 가져오기 + 데이터 캐싱 + 캐시 제어 등 데이터를 쉽고 효율적으로 관리**할 수 있는 라이브러리이다.
    

### **쉽게 설명하자면**

1. **서버 데이터 가져오기(fetching)**
    

보통 웹앱에서 API 서버에 요청해서 데이터를 받아와야 하는데 (ex. 게시글 목록, 사용자 정보, 댓글)

* **TanStack Query**를 사용하면 API 호출하는 코드를 간단하게 작성할 수 있다
    
* 내부적으로 데이터 요청 시 상태(로딩중, 성공, 실패)도 자동으로 관리해준다
    

2. **데이터 캐싱(caching)**
    

* 서버에서 받은 데이터는 **자동으로 캐시에 저장**된다
    
* 그래서 같은 데이터를 또 요청할 때, 이미 캐시에 있으면 서버에 다시 요청하지 않고 캐시 데이터를 바로 보여준다
    
* 덕분에 사용자 경험이 훨씬 빨라지고 서버 부담도 줄어서 완전 최고 !
    

### **⁉️ 여기서 캐싱이 뭐지**

→ 캐싱은 자주 쓰는 데이터를 **미리 저장해 두었다가 빠르게 꺼내 쓰는 기술이다**

### 실제 웹에서의 캐시

웹 앱에서는 데이터를 서버(API)에서 불러오는데 시간이 걸린다  
예를 들어 게시판 글을 불러오면

* 네트워크 요청 → 서버 처리 → 응답 받기 → 렌더링
    
* 이 과정은 1초 이상 걸릴 수도 있음
    

하지만 TanStack-Query 같은 라이브러리가 **그 데이터를 메모리에 저장(=캐싱)** 해두면

* 같은 데이터를 다시 보여줄 때 **서버에 다시 안 물어보고 즉시 보여줄 수 있고**
    
* 사용자는 훨씬 빠르게 화면을 보게 되고 서버 부담도 줄어든다
    

3. **캐시 자동 갱신**
    

* 캐시된 데이터가 너무 오래되면 자동으로 서버에서 최신 데이터를 다시 받아와 갱신한다
    
* 사용자가 뭔가 조작해서 데이터가 바뀌면 자동으로 최신 상태를 유지하도록 업데이트도 해준다
    

4. **데이터 상태 관리 간소화**
    

* 로딩중, 에러, 성공 상태를 별도로 `useState`, `useEffect`로 관리할 필요가 거의 없다
    
* **TanStack Query**가 알아서 상태 관리해주니까 개발자가 집중할 부분이 줄어든다
    

5. **캐시 제어 및 동기화**
    

* 개발자가 필요하면 언제든 캐시를 수동으로 갱신하거나 삭제할 수 있다
    
* 여러 컴포넌트에서 같은 데이터를 공유할 때 자동으로 동기화시켜준다
    

### Tanstack-Query에 중요한 개념

* **staleTime**(default:0)
    
    1. 데이터를 **신선하다고 간주하는 시간**이다.
        
    2. 이 시간이 지나기 전까지는 React Query는 데이터를 \*\*“신선함”\*\*으로 간주하고 **자동으로 재요청을 하지 않는다**
        
    3. 예를 들어 `staleTime: 5000`으로 설정하면 데이터를 가져온 후 **5초 동안은 재요청 없이 기존 데이터를 사용**한다
        

```javascript
useQuery(['todos'], fetchTodos, {
  staleTime: 5000, // 5초 동안은 데이터를 fresh하게 유지
});
```

* **cacheTime** (기본값: 5분)
    
    1. 쿼리가 **언마운트된(=컴포넌트가 화면에서 사라지는 것) 후에도 메모리에 데이터를 유지하는 시간**이다.
        
    2. 이 시간이 지나면 쿼리 데이터는 **가비지 컬렉션**처럼 삭제된다
        
    3. 예를 들어 `cacheTime: 10000`이면, 컴포넌트가 언마운트된 후에도 쿼리 데이터는 **10초간 캐시에 유지**된다. 그 안에 같은 쿼리가 다시 마운트되면, **기존 데이터로 빠르게 로드**된다.
        

```javascript
useQuery(['todos'], fetchTodos, {
  cacheTime: 10000, // 쿼리 언마운트 후 10초 동안 캐시 유지
});
```

### 구분되어야 하는 점

`Zustand`와 같은 상태관리 라이브러리는 클라이언트의 데이터를 관리하는 것이고 `Tanstack-Query`의 경우에는 서버 데이터를 관리하는 것이라서 사용목적이 다르다✍🏻

**사용예시**

`Zustand`

<table><tbody><tr><td colspan="1" rowspan="1"><p>✅ 다크모드 on/off<br>✅ 드롭다운 열기/닫기<br>✅ 로컬 폼 입력 값</p></td></tr></tbody></table>

`Tanstack-Query`

<table><tbody><tr><td colspan="1" rowspan="1"><p>✅ 게시글 목록 가져오기<br>✅ 댓글 불러오기<br>✅ 서버 상태 실시간 유지</p></td></tr></tbody></table>

### 📚 대표적으로 자주 사용되는 기능들

| 기술/개념 | 설명 |
| --- | --- |
| `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` 등과 같은 페이지 관련 기능이 존재해 이를 이용해 무한 스크롤을 쉽게 구현할 수 있었다..!!

```javascript
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`가 존재할 때)
    

이런식으로 사용해줬다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750050112473/7ea353c8-6da5-4a89-b19d-a601719126bd.png align="center")

지금 구인 쪽은 **fresh 상태**로 뜨고 있는 것을 확인할 수 있다..✨

### 🌱 배운점

* 리액트 쿼리에 대한 궁금증은 가득했지만 실제로 적용해보고 공부해본 적이 이번이 처음인데, **굉장히 유용한 기능이다 🥳**  
    특히 서버에서 데이터를 가져오고 관리하는 과정이 자동화되어 **불필요한 로딩 상태 관리나 캐시 처리 코드가 줄어든다는 점이 인상적이었다.**
    
* 무한 스크롤 구현도 `useInfiniteQuery` 하나로 깔끔하게 해결할 수 있었고 `queryKey`를 통해 데이터를 구분하고 캐싱하는 방식도 굉장히 체계적이라 느꼈다. 처음에는 다소 어렵게 느껴졌지만 개념 하나하나를 이해하고 나니 **React Query는 서버 데이터 관리의 필수 도구**라는 확신이 생겼다.
    
* 앞으로도 더 복잡한 기능(`invalidateQueries`, `onSuccess`, `refetch`) 등을 직접 활용하면서 계속 익혀보고 싶다! 💪
