JiSoo's Devlog

[Udemy React 완벽 가이드] 리액트 쿼리/Tanstack 쿼리 본문

Frontend/React

[Udemy React 완벽 가이드] 리액트 쿼리/Tanstack 쿼리

지숭숭숭 2024. 6. 1. 14:59

Tanstack 쿼리

Tanstack 쿼리는 HTTP 요청을 전송하고 프론트엔드 사용자 인터페이스를 백엔드 데이터와 동기화된 상태로 유지하는 데 이용하는 라이브러리

useEffect나 fetch 함수로도 작업이 가능하지만 Tanstack 쿼리를 이용하면 코드가 간결해지고 수월하게 작업이 가능하다

 

useEffect나 fetch를 사용하는 경우

많은 양의 코드를 작성해야 하는 단점이 있다

물론 커스텀 훅을 빌드해 재사용할 수 있지만 몇 가지 문제와 누락된 기능이 있다

 

Tanstack 쿼리를 사용하면 상태 관리를 비롯해 긴 코드를 작성할 필요가 없고 고급 기능을 이용할 수 있다

캐시 처리, 자체적으로 처리되는 데이터 가져오기뿐 아니라 앱을 좀 더 효율적으로 만들어 줄 모든 기능을 이용할 수 있다

 

TanStack | High Quality Open-Source Software for Web Developers

 

TanStack | High Quality Open-Source Software for Web Developers

High-quality open-source software for web developers. Headless, type-safe, & powerful utilities for State Management, Routing, Data Visualization, Charts, Tables, and more.

tanstack.com

 

npm install @tanstack/react-query

npm install @tanstack/react-query@beta // 버전5 설치

 

"dependencies": {
    "@tanstack/react-query": "^5.0.0-beta.35",
import { useQuery } from '@tanstack/react-query';

 

useQuery 훅은 자체적으로 작동해서 HTTP 요청을 전송하고 이 섹션에 필요한 이벤트 데이터를 가져오고 로딩 상태에 대한 정보를 제공한다

그래서 요청 전송 중에 발생한 오류를 알 수 있다

 

훅을 구성하는 방법은 useQuery에 객체를 전달하면 되고 이 객체에 다양한 프로퍼티를 설정할 수 있다

queryFn 프로퍼티는 쿼리 함수를 의미하고 이 함수를 이용해 실제 요청을 전송할 때 실행할 실제 코드를 정의한다

Tanstack 쿼리에는 HTTP 요청을 전송하는 로직이 내장돼 있지 않는 대신 요청을 관리하는 로직을 제공한다

요청과 관련된 데이터와 발생 가능한 오류를 추적하는 역할 등을 한다

요청을 전송하는 코드는 원하는 방식으로 직접 작성하면 된다

 

queryKey 프로퍼티가 있는데 Tanstack 쿼리는 내부에서 이 쿼리 키를 이용해 요청으로 생성된 데이터를 캐시 처리한다

이 키는 배열이고 이 값을 배열을 리액트 쿼리는 내부적으로 저장한다

 

리액트 쿼리와 useQuery 훅을 이용하려면 이런 기능을 사용할 컴포넌트를 Tanstack 쿼리가 제공하는 특수한 프로바이더 컴포넌트로 래핑해야 한다

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <RouterProvider router={router} />
    </QueryClientProvider>
  );
}

 

캐시 처리

리액트 쿼리는 응답 데이터를 캐시 처리한다

useEffect와 fetch 로직을 이용할 때는 다른 페이지로 갔다가 다시 돌아오면 새 요청을 전송해 모든 데이터를 다시 가져와야 했다

하지만 리액트 쿼리를 사용할 때는 데이터가 즉각적으로 제공된다

리액트 쿼리는 요청을 통해 얻은 응답 데이터를 캐시 처리하고 나중에 동일한 쿼리 키를 가진 다른 useQuery가 실행되면 이 데이터를 재사용한다

 

staleTime은 캐시에 데이터가 있을 때 업데이트된 데이터를 가져오기 위한 요청을 자체적으로 전송하기 전에 기다릴 시간을 설정하는 것

기본값 0을 사용하면 캐시 데이터를 사용하지만 업데이트된 데이터를 가져오기 위한 자체적인 요청을 항상 전송한다

만약 5,000으로 설정하면 5,000밀리초 동안 기다린 후에 추가 요청을 보낸다

 

gcTime은 가비지 수집 시간을 의미하며 데이터와 캐시를 얼마나 오랫동안 보관할지를 제어한다

기본값은 5분

만약 30초로 설정하면 캐시된 데이터가 30초 동안 보관된 후 폐기된다

 

const { data, isPending, isError, error } = useQuery({
    queryKey: ["events"],
    queryFn: fetchEvents,
    staleTime: 5000,
    // gcTime: 1000,
  });

↑ 리액트 쿼리는 쿼리 함수로 정의된 fetchEvents 같은 이 함수에 객체를 전달한다

export async function fetchEvents({ signal }) {

여기서 이 객체를 받고 여러 정보를 추출하기 위해 객체 구조 분해를 이용한다

const response = await fetch(url, { signal: signal }); //http요청

signal 프로퍼티에 리액트 쿼리가 제공하는 형태의 signal을 넣어주면 브라우저는 내부에서 이 취소 신호를 받아 요청을 중지할 수 있다

 

const { data, isPending, isError, error } = useQuery({
    queryKey: ["events", { search: searchTerm }],
    queryFn: ({ signal }) => fetchEvents({ signal, searchTerm }),
    enabled: false
  });

이렇게 useQuery 구성 객체에 enabled 프로퍼티를 fasle로 설정하면 쿼리가 비활성화되고 요청이 전송되지 않는다

true로 설정하면 요청이 전송되고 이게 기본 설정이다

 

isLoading은 쿼리가 비활성화됐다고 해서 true가 되지 않는다

 

 

리액트 쿼리로 새 이벤트 생성과 같이 데이터를 전송하는 것도 가능하다

useQuery는 데이터를 가져올 때만 사용한다

데이터를 전송하고 POST 요청을 전송하려면 useMutation을 이용한다

useMutation 이 데이터를 변경하는 쿼리에 최적화된다

useMutation({
    mutationFn:
    
  })

queryFn과 같이 mutationFn로 변형함수를 설정할 수 있다

mutationKey도 설정 가능하지만 필수는 아닌데 변형은 응답 데이터를 캐시 처리하지 않기 때문이다

useMutation도 객체를 반환하는데 이 객체 구조를 분해해 유용한 프로퍼티에 액세스 할 수 있다

data 프로퍼티를 설정하면 전송된 요청의 응답으로 반환된 데이터를 사용할 수 있다

 

const { mutate } = useMutation({
    mutationFn: createNewEvent,
  });

  function handleSubmit(formData) {
    mutate({ event: formData });
  }

위 함수는 폼이 제출될 때마다 요청을 전송한다

 

isPending 프로퍼티는 요청이 진행 중이면 true, 아니면 false

error 프로퍼티에는 오류의 세부 정보가 들어있다

 

const { mutate, isPending, isError, error } = useMutation({
    mutationFn: createNewEvent,
    onSuccess: () => {
      navigate("/events");
    },
  });

onSuccess 프로퍼티에 함수를 값으로 사용하고 이 함수는 변형이 성공하면 실행된다

 

queryClient.invalidateQueries({ queryKey: ["events"]});

쿼리를 무효화하는 함수를 사용해 변형을 수행할 때 특정 키를 사용하는 모든 쿼리가 최신 데이터를 사용하도록 보장한다

 

onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: ["events"],
        refetchType: "none",
      });
      navigate("/events");
    },

refetchTypenone으로 설정하면 invalidQueries를 호출할 때 기존 쿼리가 즉시 자동으로 다시 트리거되지 않도록 한다

대신 기존 쿼리는 무효화되고 다음번에 요청될 때 다시 실행된다

 

 

낙관적 업데이트

const { mutate } = useMutation({
    mutationFn: updateEvent,
    onMutate: async (data) => {
      const newEvent = data.event;

      await queryClient.cancelQueries({ queryKey: ["events", params.id] });
      const previousEvent = queryClient.getQueriesData(["events", params.id]);

      queryClient.setQueryData(["events", params.id], newEvent);
      return { previousEvent: previousEvent };
    },
    onError: (error, data, context) => {
      queryClient.setQueryData(["events", params.id], context.previousEvent);
    },
    onSettled: () => {
      queryClient.invalidateQueries(["events", params.id]);
    },
  });

onMutate에서의 함수는 mutate를 호출하는 즉시 실행된다

onMutate에서 리액트 쿼리에 의해 캐시된 데이터를 업데이트한다

setQueryData를 호출해 이미 저장된 데이터를 응답을 기다리지 않고 수정할 수 있다

cancelQueries로 특정 키의 모든 활성 쿼리를 취소할 수 있다

getQueryData 메소드는 현재 저장된 쿼리 데이터를 가져오고 이를 실행한 다음 새 데이터로 설정한다

 

onError 프로퍼티는 리액트 쿼리에서 자동으로 전달되는 몇 가지 입력을 간단하게 수신한다

실패하게 되는 error 객체, mutation에 전송된 data, context 객체를 수신한다

onSettled 프로퍼티는 성공 여부와 상관없이 mutation이 완료될 때마다 호출된다

 

낙관적 업데이트를 실행하고 오류가 발생하면 롤백을 하더라도 mutation이 완료될 때마다 백엔드에서 최신 데이터를 가져왔는지 확인할 수 있다

백엔드와 프론트엔드 간에 동기화되지 않은 경우 리액트 쿼리에 데이터를 내부적으로 다시 가져오도록 강제하여 다시 동기화된다

 

728x90