TanStack Query

서버상태 관리를 위한 라이브러리

데이터를 패칭하고 캐싱, 동기화, 무효화 등의 기능을 제공

이전보다 비동기 로직을 간편하게 작성하고 유지보수성을 높일 수 있음

 

주요 기능

1) 데이터 캐싱: 동일한 데이터를 여러번 요청하지 않도록 캐싱하여 성능을 향상시킴

2) 자동 리페칭: 데이터가 변경되었을 때 자동으로 리페칭하여 최신상태를 유지

3) 쿼리 무효화: 특정 이벤트가 발생했을 때 쿼리를 무효화하고 데이터를 다시 가져올 수 있음

 

 

사용해보기

주요 hooks

1) get: useQuery

2) modify: useMutation

3) refresh: invalidateQueries

 

 

설치

yarn add @tanstack/react-query

 

Provider

QueryClientProvider로 서버상태의 영향권에 들어올 App 컴포넌트 등을 감싸준다

client 속성으로 기능 집합체인 인스턴스를 전달해줘야 함

 

const queryClient = new QueryClient();

<QueryClientProvider client={ queryClient }>
  <App/>
</QueryClientProvider>

 

useQuery()

두 인자를 전달 -> queryKey, queryFn

이런 처리도 가능해짐

 

useMutation()

변화가 생겨서 update해야할 때 어떤 동작을 해야할지 지정해줘야 함

1) useMutation 훅을 이용해 mutation 객체를 만든다

 

2) addTodo 로직을 추가하고, mutation.mutate로 사용

- newTodo는 input value를 제어하고있는 state

 

* ) 혹은 구조분해 할당으로 mutation.mutate를 바로 받아둬도 됨

 

invalidateQueries()

위 과정까지만 진행했을 땐, 서버에는 새로운 데이터가 추가됐지만 우리 눈에 보이는 클라이언트는 아직 업데이트 되지 않음

따라서 refresh 과정인 invalidateQueries를 처리해줘야 함

 

1) 기존에 QueryClientProvider 레벨에서 만들어뒀던 queryClient를 useQueryClient()를 통해 받아온다

 

2) matate에서 onSuccess에 실행할 처리를 작성해준다 queryClient.invalidateQueries에는 queryKey를 배열형태로 넣어준다

-> 쿼리키는 데이터를 캐싱하는 기준, 이제 이 쿼리키가 더이상 유효하지 않다고 처리해주면서 현재 서버의 todos로 클라이언트를 갱신해주는 것

 

 

QueryClient의 동작 과정

useQuery()

- 캐시된 컨텍스트에 접근해서 값을 받아옴

- 아직 없을 땐 undefined

- 없으면 api 서버로 GET 요청

- 서버가 todos (해당하는 데이터)로 컨텍스트에 캐싱 처리

- 변경된 상태가 페이지 컴포넌트에 반영되기 전에 리렌더링이 일어남

- 리렌더링이 일어나면서 useQuery가 다시 실행되고, 이제는 컨텍스트에 캐시된 값이 있으니까 그 값이 반영됨

 

그 다음에 렌더링되는 컴포넌트에서 같은 queryKey에 대해 useQuery를 사용하는 경우, 이미 위의 과정에서 캐시된 데이터가 컨텍스트에 있기때문에 위의 과정을 거치지 않고 바로 컨텍스트에서 값을 받아올 수 있음 (isPending === true인 과정을 거치지 않음)

 

invalidateQueries()

- 이후 queryKey에 대한 refresh 요청이 일어났을 때, 다시 API 서버에 해당 값을 요청하고 컨텍스트에 서버의 새로운 값이 들어오면서 해당 값을 사용하고 있는 컴포넌트들에 새로운 값들이 반영됨

 

더보기

주요 흐름 설명

  1. A컴포넌트에서
    1. A 페이지 컴포넌트에서 useQuery가 실행됩니다. 이 때, todos 라는 Query Key를 기준으로 캐시 컨텍스트에 데이터를 요청해요.
    2. 최초 상태에는 todos라는 Query Key에 아무 값도 저장되어 있지 않기 때문에 const { data } = useQuery~~ 의 data는 undefined가 됩니다.
    3. 이후, getTodos(query function)을 호출하고, 실행이 완료되면 외부에서 가져온 todos 데이터를 캐시 컨텍스트 안에 담습니다. 이 과정을 위의 이미지에서는고 표현했네요.
    4. [”todos”]에 대한 데이터로 캐싱처리 한다
    5. A 페이지 컴포넌트에 값을 반영시키기 전에 re-rendering이 일어납니다.
    6. React 컴포넌트는 상태나 props가 변경되면 다시 렌더링되죠? useQuery hook 역시 데이터 패칭 상태가 변경될 때 컴포넌트를 다시 렌더링합니다. 데이터 패칭 상태는 곧 배워요 😎
    7. useQuery가 다시한번 실행되며, todos 라는 Query Key를 기준으로 캐시 컨텍스트에 데이터를 요청해요.
    8. 이제 캐싱된 데이터가 존재하기 때문에 값을 반환하여 const { data } = useQuery~~ 의 data에는 값이 담기게 됩니다.
  2. B컴포넌트에서
    1. B 페이지 컴포넌트에서 useQuery가 실행됩니다. 이 때, todos 라는 Query Key를 기준으로 캐시 컨텍스트에 데이터를 요청해요.
    2. 이미 캐싱된 데이터가 존재하기 때문에 값을 반환하여 const { data } = useQuery~~ 의 data에는 값이 담기게 됩니다.
  3. C컴포넌트에서
    1. C 페이지 컴포넌트에서 어떠한 액션에 의해 addTodo 라는 api 호출이 되며 데이터 갱신을 시도합니다.
    2. 성공적으로 완료되었습니다. 이 때, 자동으로 useQuery에서 썼던 캐시데이터가 갱신되지 않아요! 반드시 해당 queryKey를 기준으로 invalidateQueries 처리를 해줘야 기존에 가져왔던 오래된 데이터를 새 것으로 갈아끼게 됩니다.
    3. invalidateQueries 처리를 하게 되면, useQuery를 이용하여 캐시데이터를 활용했던 모든 곳이 새로운, fresh한 데이터를 구독하게 돼요.