[TIL] 06/10 :: TanStackQuery

yeseul·2024년 6월 10일

<TIL>

목록 보기
20/43

* TanStackQuery

= ReactQuery

✔️ 등장배경

  • 비동기 로직의 복잡성 해결 필요
    • 기존의 useEffect, useState 를 사용한 비동기 데이터 처리 방식은 상태관리가 복잡하고 코드중복이 많아 유지보수가 어려웠다.
    • Redux Thunk 와 같은 미들웨어를 사용해도 비동기 로직의 테스트가 복잡하고 보일러플레이트 코드(반복적으로 사용되는 코드의 템플릿)가 많이 생김
  • 서버상태관리의 어려움
    • 클라이언트 상태와 달리 캐싱, 동기화, 재검증 등 관리해야 할 요소가 많아서 관리가 어려움
    • TanStack Query 등장 -> 서버상태 관리를 쉽게 해주고 복잡한 비동기 로직을 단순화해준다.
  • 리액트 쿼리는 리액트 어플리케이션 내에서 데이터를 받아오고(fetching), 캐싱하고(caching), 서버상태를 동기화(synchronizing) 및 업데이트(update) 할 수 있도록 도와준다.
  • 리액트 어플리케이션의 경우 크게 3가지의 상태를 관리하게 되는데,
    특정 컴포넌트 내부에서 사용하는 local state,
    전역으로 관리하는 global state,
    서버에서 받아온 데이터를 관리하는 server state가 있다.
    -> React-query는 이 중 server state를 관리하는 것에 최적화된 라이브러리이다.

✔️ 개념

  • 서버상태관리 라이브러리
    • TanStack Query 는 서버상태를 관리하기 위한 라이브러리이다.
    • 데이터를 패칭하고 캐싱, 동기화 및 업데이트, 무효화 등의 기능을 제공한다.
    • 비동기 로직을 간편하게 작성하고 유지보수성을 높일 수 있다.

  • 주요기능
    • 데이터 캐싱 : 동일한 데이터를 여러 번 요청하지 않도록 캐싱하여 성능을 향상시킨다.
    • 자동 리페칭 : 데이터가 변경되었을 때 자동으로 리페칭하여 최신상태를 유지한다.
    • 쿼리 무효화 : 특정 이벤트가 발생했을때 쿼리를 무효화하고 데이터를 다시 가져올 수 있다.

fetching : 데이터를 받아오는 중인 상태.
fresh : 최신화 상태의 데이터. 컴포넌트의 상태가 변경되어도 다시 불러오지 않는다.
stale : 최신화가 필요한 데이터. 컴포넌트의 상태가 변경되면 refetch를 한다.
inactive : 현재 사용중이지 않은 상태의 쿼리. cacheTime이 만료되면 deleted 상태가 된다.
deleted : 가비지 컬렉터에 의해 삭제된 상태

  • 최신의 데이터 : fresh 한 데이터
  • 기존의 데이터 : stale 한 데이터
    -> 리액트쿼리는 stale 데이터를 fresh 데이터로 갱신해 준다. stale 데이터란 실제 서버 데이터와 클라이언트 데이터가 서로 일치하지 않을 가능성이 존재하는 데이터를 의미한다. 리액트 쿼리는 사용자가 마우스로 어떤 행동을 하거나 개발자가 지정한 일정 시간이 지난 뒤에는 데이터가 stale 데이터라고 판단하여 다시 서버에 요청하여 fresh 데이터로 갱신하는 기능을 제공한다.
  • stale로 분류된 쿼리들이 다시 리패치(데이터 새로 불러옴) 되는 경우
    • 새로운 쿼리가 만들어졌을때 (쿼리 키에 전달되는 state 가 변경되어 리렌더링 일어났을때)
    • 윈도우가 다시 포커스 되었을때
    • 네트워크가 다시 연결되었을때
    • 리페치 인터벌이 설정되었을때
  • React Query로 받아온 데이터는 기본적으로 캐싱이 되어 있지만 바로 stale한 상태가 된다. 이는 기본적으로 cacheTime이 5분, staleTime이 0으로 설정되어 있기 때문이다.
    설정을 다르게 하고 싶다면 option에서 해당 값들을 변경할 수 있다.

staleTime : 데이터가 fresh에서 stale 상태로 변경되는데 걸리는 시간
cacheTime : 데이터가 inactive 상태일 때 deleted가 되기전 caching되는 시간.


* 서버상태 관리시 아래의 세가지 과정이 있어야 한다.

1. get : Read -> 가져오는거

=> useQuery 훅
: 쿼리 키와 비동기 함수(패칭 함수)를 인자로 받아 데이터를 가져오고, 로딩 상태, 오류 상태, 그리고 데이터를 반환한다.

  • 로딩 상태, 오류 상태 등을 자동으로 반환하기 때문에 redux-thunk에서처럼 일일히 모든 상태를 직접 세팅할 필요가 없다.
  • data, error, isFetching, refetch 등 다양한 반환값과 옵션들을 가지고 있다.
    • 헷갈릴만한 값으로 isFetching과 isLoading이 있는데 둘의 차이는 아래와 같다.
      둘은 목적이 조금 다르기에 Loading indicator를 어떤 방식으로 나타내냐에 따라 적합한 값을 취하면 좋을 것 같다.
      - isFetching : 데이터 요청중인 상태 (boolean)
      - isLoading : 캐싱이 되어 있지 않은 데이터를 요청중인 상태
      즉, 최초 데이터 요청시 isLoading, isFetching이 true인 상태이고 이후에는 캐싱이 되어있기에 isFetching은 true, isLoading은 false라고 생각하면 된다.

2. modify : Create, Update, Delete -> 수정하는거

=> useMutation (mutation : 돌연변이. -> 변경이 일어난다는 뜻)
: 데이터를 생성, 수정, 삭제하는 등의 작업에 사용되는 훅

  • CUD에 대한 비동기 작업을 쉽게 수행하고, 성공 또는 실패 시에 추가적인 작업을 실행할 수 있다.
  • 변경된 이후 데이터를 최신화하기 위해 onSuccess 옵션을 사용할 수 있으며 queryClient 내부 함수인 invalidateQueries를 통해 Key를 stale한 상태로 변경하면 변경과 동시에 새로운 정보로 업데이트 할 수 있다.
    • queryClient.invalidateQueries() : Key가 포함된 모든 쿼리를 stale한 상태로 변경. mount가 되었을 때 refetch한다.
    • queryClient.refetchQueries() : Key가 포함된 모든 쿼리를 refetch. { active: true } 등의 옵션을 통해 조건을 변경할 수 있다.

3. refresh

=> invalidateQueries (유효화 하지 않는다. -> 무효화 한다.)
: 특정 쿼리를 무효화하여 데이터를 다시 패칭하게 하는 함수.

  • 주로 useMutation과 함께 사용하여 데이터가 변경된 후 관련 쿼리를 다시 가져오도록 한다.
  • 이를 통해 데이터가 항상 최신 상태로 유지될 수 있도록 도와준다.
    ex) 새로운 할 일을 추가한 후 기존의 할 일 목록을 다시 가져오도록 하는것
  • 데이터를 캐싱하는 기준(서버에 다시 요청하지 않고 가지고 있는 기준)은 Query Key 이다.
  • Query Key가
    더이상 유효하지 않다고 알려주는게 invalidateQueries 이다.
  • invalidateQueries 할때는 반드시 쿼리키를 넣어줘야한다.

✔️ 설치

yarn add @tanstack/react-query

* 샘플코드

import {
  useQuery,
  useMutation,
  useQueryClient,
  QueryClient,
  QueryClientProvider,
} from '@tanstack/react-query'
import { getTodos, postTodo } from '../my-api'

// Create a client
const queryClient = new QueryClient()

function App() {
  return (
    // Provide the client to your App
    <QueryClientProvider client={queryClient}>
      <Todos />
    </QueryClientProvider>
  )
}

function Todos() {
  // Access the client
  const queryClient = useQueryClient()

  // Queries
  const query = useQuery({ queryKey: ['todos'], queryFn: getTodos })

  // Mutations
  const mutation = useMutation({
    mutationFn: postTodo,
    onSuccess: () => {
      // Invalidate and refetch
      queryClient.invalidateQueries({ queryKey: ['todos'] })
    },
  })

  return (
    <div>
      <ul>{query.data?.map((todo) => <li key={todo.id}>{todo.title}</li>)}</ul>

      <button
        onClick={() => {
          mutation.mutate({
            id: Date.now(),
            title: 'Do Laundry',
          })
        }}
      >
        Add Todo
      </button>
    </div>
  )
}

render(<App />, document.getElementById('root'))
  1. 리액트쿼리를 사용하고자 하는 컴포넌트에 QueryClientProvider contextAPI로 감싼다.

  2. queryClient 생성자를 client props로 넘겨준다.

  3. useQueryClient로 queryClient를 생성한다.

  4. useQuery를 사용하여 data를 호출한다.

  5. useMutation으로 데이터를 insert, update, delete 한다.

  6. 성공시 invalidateQueries 로 무효화 시킨다.

0개의 댓글