본문 바로가기
공부/React & React Native

React-Query 찍어먹기

by Piva 2024. 3. 4.
  • 최근 React-Query를 쓰는 회사들이 꽤 많음을 짐작할 수 있었다.
  • 마침 종종 참고하던 강의에 React-Query 내용이 있어, 강의를 듣고 간단히 써본 것을 정리한다.

React-Query(Tanstack-query)

: HTTP 요청을 보내고, 백엔드 데이터와 프론트엔드의 UI를 동기화하게 해주는 서버 상태 관리 라이브러리.

  • 서버에 요청을 보내는 것은 굳이 이 라이브러리를 쓰지 않아도 가능하지만, React-query를 사용하면 간편함과 다양한 이점을 얻을 수 있음.
  • ex) 코드 길이 감소, 데이터 변경 시 자동으로 새 데이터 fetch, 데이터 캐싱 등...
  • React-Query에서 직접 HTTP 요청을 보내는 것이 아닌, 요청 결과로 얻은 데이터와 에러, 로딩 상태, 캐싱 등을 담당하는 역할을 수행한다.

 

QueryClientProvider

: react-query를 사용하기 위해서는 App 컴포넌트를 QueryClientProvider로 감싸줘야 한다.

  • QueryClientProvider는 client를 필수 prop으로 가지며, 여기에는 QueryClient 인스턴스를 넘긴다.
import { QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      /* ... */
    </QueryClientProvider>
  );
}

export default App;

 

 

useQuery

: 커스텀 훅이나 컴포넌트 내부에서 쿼리를 정의할 때 사용하는 React-query의 훅. 필수 인자로 아래의 목록을 받는다.

  • queryKey: 각 쿼리의 고유한 키. 내부적으로 데이터를 다시 fetch하거나, 캐싱하거나, 어플리케이션 전체적으로 쿼리를 공유할 때 사용한다.
  • queryFn: 데이터를 resolve 하거나 에러를 처리하는 함수. useQuery가 QueryFuntionContext 오브젝트*를 queryFn에 넘긴다.
  • enabled: useQuery를 실행할지, 말지를 결정하게 하는 프로퍼티. 이걸 false로 설정하면 useQuery가 자동적으로 실행되지 않는다. 대신, useQuery 내부적으로는 data가 없는 것(아직 데이터를 가지고 오는 중)으로 판단, isPending을 true로 반환함.
    • 이 때 isPending 대신 사용할 수 있는 것이 isLoading.
      (참고: 링크)
* QueryFunctionContext: 아래의 프로퍼티들을 갖는다.
- queryKey: useQuery의 그 키.
- signal: useQuery의 실행을 중단할 수 있게 해준다.
- meta: query에 대한 추가 정보를 넣을 수 있다.

 

  • useQuery는 윈도우를 전환할 때마다 자동적으로 데이터를 다시 가져온다(=== 서버와 데이터를 동기화하는 것이 가능하다).
  • 데이터를 알아서 캐싱하는 것이 가능하다.
    • ex) useEffect를 사용하여 데이터를 가져올 경우, 페이지를 오갈 때마다 데이터를 그때그때 다시 fetch한다(따라서 빈 화면 혹은 로딩 UI가 보이게 될 것).
    • useQuery를 사용하면 화면 전환 시 캐시된 데이터를 사용하기 때문에 바로 화면을 보여줄 수 있게 된다.
  • useQuery의 필수 옵션인 queryKey는 동적으로 할당하는 것이 가능하다. 이 경우, React-query가 같은 query 안에서 서로 다른 데이터에 대해 각각 다른 키 값을 갖게 하는 것이 가능하다.
  • useQuery는 위에서 언급한 isPending, isLoading, isError 및 data 등의 값을 반환한다.
    • 요청의 현재 상태 및 요청의 결과로 서버로부터 받아 온 데이터 등을 확인할 수 있다.
// 아래의 useQuery는 요청 상태 및 에러, 요청한 데이터를 반환한다
const { data, isPending, isError, error } = useQuery({
  queryKey: ["events", id], // 쿼리 키를 동적으로 할당할 수 있다
  queryFn: ({ signal }) => fetchEvent({ id, signal }), // id값에 해당하는 이벤트를 가져오는 함수
});

 

 

 

useMutation

: 요청을 보내는 useQuery와 달리, 데이터 작성/삭제/변경 등에 사용되는 훅. 아래의 인자들을 받을 수 있다. useQuery와 달리 useMutation은 별도의 필수 key 값을 요청하지 않는다는 차이점 또한 있다.

  • mutationFn: 데이터 작성/삭제/변경을 위해 실행할 함수.
  • onSuccess: 요청이 성공적으로 수행되었을 때 실행할 함수.
  • onMutate: mutation 함수가 실행되기 전에 실행되는 함수. mutation 함수가 받는 인자를 그대로 받는다.
  • onSettled: mutation의 결과에 상관없이(즉, 성공했든 실패했든) 실행되는 함수.

 

  • useMutation은 mutate 함수를 반환한다.
    • mutate 함수를 사용해 어떤 곳에서든 서버 작업을 수행할 수 있게 한다.
// 아래의 useMutation은 mutate 함수, 요청 상태 및 에러를 반환한다
const { mutate, isPending, isError, error } = useMutation({
  // mutationFn으로 새 이벤트를 서버에 작성하는 함수를 넘긴다
  // 추후 useMutation이 반환하는 mutate 함수를 통해 원하는 곳에서 새 이벤트를 작성할 수 있다
  mutationFn: createNewEvent,
});

 

 

QueryClient

: 공식 문서에서 설명하는 바에 따르면 캐시와 관련된 다양한 작업을 수행할 수 있다. (참고) 여기에선 강의에서 다룬 메서드들을 간단히 기록한다.

  • invalidateQueries(): 인자로 전달한 Query key에 해당하는 쿼리들이 outdated 된 것으로 간주, 해당 쿼리들을 다시 refetch한다.
    • ex) 위의 코드를 예시로, 특정 이벤트를 추가한 후에는 전체 이벤트 리스트가 변경되었으므로 이벤트 리스트를 가져오는 쿼리를 다시 refetch하도록 만들 수 있다.
const { mutate, isPending, isError, error } = useMutation({
  mutationFn: createNewEvent,
  // 추가 부분: 요청이 성공하면 events 쿼리 키를 갖는 쿼리들을 다시 refetch 시킨다
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ["events"] });
  },
});

 

  • cancelQueries(): 전달받은 Query key에 해당하는 쿼리들을 취소시킨다.
  • setQueryData(): 쿼리의 캐시된 데이터를 바로 업데이트하는 동기 함수.

 


  • 서버 통신과 관련한 다양한 기능을 갖고 있어서 처음 써보는 입장에선 신기했던 편.
  • 언젠가 적극적으로, 제대로 프로젝트에서 써보고 싶다는 생각이 든다.