[React] React Query

mynoseis3·2024년 4월 4일
0

react

목록 보기
9/9
post-thumbnail

공식 문서 참고
https://tanstack.com/query/latest
현재 시점은 5버전 !

React Query

React Query는 React Application에서 서버 상태를 불러오고, 캐싱하며, 지속적으로 동기화하고 업데이트하는 작업을 도와주는 라이브러리이다.

React Query는 Redux나 Context API 등의 상태 관리 도구와 함께 사용할 수 있지만, 비동기 데이터 로딩 및 관리에 특화되어 있어 별도의 상태 관리 라이브러리 없이도 사용할 수 있다.

주요 기능

  • API 호출 및 데이터 캐싱
    API 호출을 관리하고 요청된 데이터를 캐싱하여 애플리케이션의 성능을 향상시킨다.
    데이터가 로컬 캐시에 저장되어 있으면, 이전에 호출된 데이터를 재사용하여 불필요한 요청을 줄인다.

  • 데이터 상태 관리
    서버에서 받은 데이터의 상태를 추적하고 관리하여 로딩, 에러, 성공 등의 상태를 처리, 이를 통해 UI를 업데이트하고 사용자에게 적절한 피드백을 제공할 수 있다.

  • 자동화된 데이터 재로드
    데이터 유효성을 유지하기 위해 일정 시간이 지난 후에 자동으로 데이터를 다시 로드할 수 있다. 이는 실시간 업데이트나 데이터의 최신 상태를 유지하는 데 유용하다.

  • 쿼리 캐시 인터페이스
    React Query는 캐시된 데이터에 접근하고 조작할 수 있는 강력한 인터페이스를 제공한다. 이를 통해 개발자는 쉽게 데이터를 가져오고 조작할 수 있다.


프로젝트 준비

리액트 쿼리 활용 하기 전에 기본 셋팅 하기 !

  • 리액트 쿼리 설치
    npm i @tanstack/react-query

  • devtools 설치
    npm i @tanstack/react-query-devtools

  • index.js 설정

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
const root = ReactDOM.createRoot(document.getElementById('root'));
const queryClient = new QueryClient();
root.render(
  <QueryClientProvider client={queryClient}>
    <App />
    <ReactQueryDevtools initialIsOpen={false} />
  </QueryClientProvider>,
);


reportWebVitals();

앱을 실행해보면 해변 아이콘이 보인다 !!
npm start

이제 서버 통신과 관련된 테스트를 해보기 위해 npm의 json-server 설치
https://www.npmjs.com/package/json-server
npm install json-server

json-sever를 이용하려면 임의의 데이터가 필요
프로젝트 루트 경로에 db.json 파일 추가

json-server 실행 명령어 ( 기본 포트는 3000 이다.)
json-server --watch db.json

포트 변경 명령어
json-server --watch db.json --port 3004

서버를 실행했을 때 에러 메세지가 나오면 앞에 npx를 붙이고 실행하면 에러가 안 난다.

localhost:3004/posts로 들어가보면 ! 입력한 db.json 데이터파일이 나타나고 있다.

++ 설명 생략 ( 라우터 돔 설치, 페이지 컴포넌트 2개 만들기(홈페이지,리액트쿼리페이지), index.js설정, app.js에서 연결 )

api 호출 테스트를 위해 axios 설치
npm i axios

이제 준비 완료 테스트 꼬고


React-Query에서 data fetching을 위해 제공하는 대표적인 기능으로
GET 에는 useQuery가, PUT, UPDATE, DELETE에는 useMutation이 사용된다.

여기선 useQuery에 대해 알아보자

useQuery

useQuery 훅은 데이터 조회 작업에 사용되며
하나의 매개변수를 갖는데 이 매개변수는 객체타입이다.

queryKey: 쿼리의 식별자
'posts'라는 데이터에 관한 것임을 나타내기 위한 것으로 유니크해야 한다.
이 식별자는 캐시와 같은 작업에서 사용된다.

queryFn: 실제 데이터를 가져오는 함수
여기선 Axios를 사용하여 http://localhost:3004/posts 엔드포인트로 GET 요청을 보내고, 해당 데이터를 반환한다.

import axios from 'axios';
import React from 'react';
import { useQuery } from '@tanstack/react-query';

const ReactQueryPage = () => {
  const { data } = useQuery({
    queryKey: ['posts'],
    queryFn: () => {
      return axios.get('http://localhost:3004/posts');
    },
  });
  console.log('ddd', data);
  return <div>React Query Page</div>;
};

export default ReactQueryPage;

리액트쿼리는 서버 상태를 유용하게 해주는 라이브러리이므로
데이터뿐만 아니라 isLoading과 같은 상태 값도 받을 수 있다.

콘솔에서 확인해보니 내가 isLoading을 선언하지도 않았는데 자동으로 true, false 처리가 알아서 되는 것을 확인할 수 있었다.

서버상태로 에러도 있다. isError, error도 받을 수 있는데 테스트를 위해 url을 틀리게 넣고 에러를 발생시켜보면 에러에 대한 정보도 받아오는 걸 확인할 수 있다.

이를 통해 알 수 있는 사실
1. isError, error를 통해 에러가 왔는지, 에러메세지가 무엇인지 알 수 있다.
2. api 호출 시도를 3번을 더 하고 있음
api 호출에 실패해도 재시도해주는 것도 리액트 쿼리 기능중 하나 !!
몇번을 더 시도할 지 재시도 횟수를 지정해 줄 수도 있음
기본 재시도 횟수는 3번이다.
1번은 원래 호출하는 횟수/1번이 실패할 때 3번을 더 시도하므로 총 4번이 보인 것

retry를 지정해주고 다시 확인해보면 기본 시도하는 횟수 1번, retry로 지정한 횟수 1번을 시도해서 총 2번을 시도한 것을 확인할 수 있다.

이제 데이터를 화면에 보여주자


//...

if (isLoading) {
    return <h1>Loading...</h1>;
  }
  if (isError) {
    return <h1>{error.message}</h1>;
  }

  return (
    <div>
      {data.data.map((item) => (
        <div>{item.title}</div>
      ))}
    </div>
  );
};

data.data - > data 로 바로 가져올 방법도 있다.

select 옵션 사용하기

 const { isLoading, data, isError, error } = useQuery({
    queryKey: ['posts'],
    queryFn: () => {
      return axios.get('http://localhost:3004/posts');
    },
    retry: 1,
    select: (data) => {
      return data.data;
    },
  });


... // select 옵션을 사용하면 data로 바로 사용하게 할 수 있음 ! 

return (
    <div>
      {data.map((item) => (
        <div>{item.title}</div>
      ))}
    </div>
  );

프로젝트 할 때 자주 쓰이는 옵션들에 부딪혀보자요 !!

캐시관리

React Query에서는 캐시를 활용하여 데이터를 저장하고, 이를 통해 성능을 최적화하고 더 빠른 데이터 로딩을 제공한다.

사용자가 요청한 데이터를 캐시에 저장해두고, 동일한 요청이 발생할 때 서버에 요청을 보내지 않고 캐시된 데이터를 사용하게 된다.
이를 통해 애플리케이션의 반응 속도를 향상시키고 사용자 경험을 향상시킬 수 있다.
일반 fetch로 이루어진 컴포넌트는 캐싱 되지 않고 리액트쿼리를 활용한 컴포넌트만
캐싱되는 걸 확인할 수 있었다. 추가적으로 리액트 쿼리를 활용한 컴포넌트는 Home과 왔다갔다할 때 Loading이 나타나지 않는다. 왜냐면 이미 캐시된 데이터를 사용하게 되니깐 !! (캐시는 눈속임 같은 느낌이다. 일단 저장되어 있는 데이터로 화면에 보여주고 뒷단에서는 api호출이 이루어지고 있는 것 !!=>로딩스피너가 계속 보여지는걸 방지해주기에 좋은 역할을 해주는 친구같다.)

gcTime 옵션을 사용하여 캐시의 수명을 조절할 수 있다.

const ReactQueryPage = () => {
  const { isLoading, data, isError, error } = useQuery({
    queryKey: ['posts'],
    queryFn: () => {
      return axios.get('http://localhost:3004/posts');
    },
    retry: 1,
    select: (data) => {
      return data.data;
    },
    gcTime: 3000,
  });

inactive 상태일 때 설정한 3초 후에 캐시가 비워지는 걸 확인할 수 있다.

api 호출 상태

참고

리액트 쿼리는 데이터의 상태에 대해 유통기한을 나누게 된다.

Fetching(api가 호출되고 있을 때) - > Fresh(막 데이터가 도착했을 때) - >
Stale(데이터가 도착한 후 시간이 지났을 때)-> Inactive(캐시를 사용하지 않는 상태/ 캐시삭제 카운트 들어감)

리액트 쿼리의 장점중 하나가
fresh한 상태인 api는 다시 호출하지 않게 통제하고 데이터가 유통기한이 끝난 stale 상태일 때 다시 api 호출을 하도록 조절할 수 있다는 점이다.
이러면 기대되는 효과는 예를 들어 한 번 api호출로 받아온 데이터중 잘 변하지 않는 값들인 경우엔 여러번 호출을 계속 할 필요가 없을 때 fresh 상태로 길게 유지되게 해주면 불필요한 요청을 줄일 수 있게 된다.

staleTime

staleTime 옵션
캐시된 데이터가 유효하지 않아서 다시 가져와야 하는 시간(초)
즉, 데이터가 캐시된 이후로부터 "staleTime" 이후에는 데이터가 새로 고쳐져야 한다고 표시된다.

const { isLoading, data, isError, error } = useQuery({
    queryKey: ['posts'],
    queryFn: () => {
      return axios.get('http://localhost:3004/posts');
    },
    retry: 1,
    staleTime: 7000,
    select: (data) => {
      return data.data;
    },
    gcTime: 3000,
  });

staleTime을 7초로 설정하고 테스트해보면 api호출을 하면 -> fresh 상태에 있다가 ->
7초가 지나면 -> stale 상태로 변하는 걸 확인할 수 있다.
추가적으로 fresh 상태일 땐 api호출을 하지 않는 것을 알 수 있었다.
(fresh상태일 땐 캐시에서 데이터를 가져와서 화면에 보여줌)
이 옵션을 사용하여 캐시된 데이터의 적절한 신선도를 유지할 수 있으며, 네트워크 요청을 최소화하여 성능을 향상시킬 수 있게 된다.

결론 : staleTime은 자주 호출하지 않아도 되는 api 부분에서 사용하기

💡 staleTime보다 gcTime이 짧을 때 api 호출은 어떻게 될까 ?!
staleTime이 gcTime보다 길면 gcTime 이후 cache가 비어지고 아직 staleTime이 끝나지 않았지만 api를 호출하게 된다.
-- > 캐시가 없으면 아무리 staleTime을 길게 줘도 어차피 api호출이 일어날 수 밖에 없어서 쓰는 의미가 사라져버림
staleTime의 원기능을 활용하려면 캐시가 더 길게 살아남게 staleTime < gcTime이 되게 해주자 !

refetchInterval

refetchInterval 옵션은 데이터를 주기적으로 다시 가져오는 간격(초)을 설정한다.

테스트로 3초 설정 - > 3초마다 api를 호출하는 것을 확인할 수 있다.

refetchOnMount

refetchOnMount: false 옵션을 사용하면 컴포넌트가 마운트될 때 데이터를 다시 가져오지 않도록 설정한다.

페이지가 로드될 때 초기 데이터를 미리 가져온 후
사용자가 상호 작용하는 동안 데이터를 업데이트하지 않아야 할 때 사용할 수 있다.
초기 데이터 로드가 빈번하게 변경되지 않는 경우에도 리소스를 절약할 수 있음
원하는 경우에만 데이터를 다시 가져올 수 있게 조절할 수 있게 된다.
기본값은 true이다.

refetchOnWindowFocus

refetchOnWindowFocus: true 옵션은 사용자가 브라우저 창에 다시 포커스될 때 데이터를 자동으로 다시 가져오도록 설정한다.

이는 사용자가 애플리케이션 창을 다시 활성화할 때 최신 데이터를 가져와 사용자 경험을 향상시키는 데 유용하다.

사용자가 다른 탭으로 전환한 후 다시 애플리케이션으로 돌아올 때마다 데이터를 다시 가져오는 것이 사용자에게 부담이 될 경우도 있으므로 이 옵션을 사용할 때는 사용자 경험을 고려하여 신중하게 설정해야 한다.

버튼 클릭으로 api호출하기

useQuery에서 지원해주는 refetch 함수 활용하기

버튼을 클릭할 때 refetch 함수를 호출하여 데이터를 다시 가져온다.
이렇게 하면 사용자가 직접 버튼을 클릭할 때마다 최신 데이터로 화면을 갱신할 수 있다.

import axios from 'axios';
import React from 'react';
import { useQuery } from '@tanstack/react-query';

const ReactQueryPage = () => {
  const {  data, refetch } = useQuery({
    queryKey: ['posts'],
    queryFn: () => {
      return axios.get('http://localhost:3004/posts');
    },
    retry: 1,
    select: (data) => {
      return data.data;
    },
  });

  return (
    <div>
      {data.map((item) => (
        <div>{item.title}</div>
      ))}
      <button onClick={refetch}>post리스트 다시 들고오기</button>
    </div>
  );
};

export default ReactQueryPage;

++ 처음에는 api호출 안 되게 하기 ( 버튼 클릭했을 때만 api호출되게 )

enabled: false 옵션을 추가해주면 쿼리의 활성화 여부를 결정해주는 역할을 하여
맨 처음에는 api호출이 안 되게 해줄 수 있다.
테스트해보면 처음엔 api호출이 안 되고 버튼을 눌렀을 때만 호출이 되는 것을 확인할 수 있다.

++ 특정 사용자가 로그인한 경우에만 데이터를 가져와야 한다고 가정했을 때 "enabled" 옵션을 사용하여 로그인 상태에 따라 쿼리를 활성화하거나 비활성화할 수 있다.

const { data, isLoading } = useQuery('userData', fetchData, {
  enabled: isLoggedIn // isLoggedIn이 true일 때만 쿼리를 활성화
});

위의 예시에서는 isLoggedIn이 true일 때만 userData 쿼리를 활성화 - > 로그인되지 않은 사용자에게는 데이터를 가져오지 않고 필요하지 않은 네트워크 요청을 줄일 수 있게 됨

"enabled" 옵션은 쿼리를 효율적으로 관리하고 필요한 시점에만 활성화할 수 있으니까 적재적소에 잘 사용해보면 좋을 듯 !!

디테일 데이터 불러오기

queryKey 안에 queryKey: ['posts', 1] 1번이라는 값을 넣어주고 테스트

 const { isLoading, data, isError, error, refetch } = useQuery({
    queryKey: ['posts', 1],
    queryFn: (queryData) => {
      console.log(queryData);
      const id = queryData.queryKey[1];
      return axios.get(`http://localhost:3004/posts/${id}`);
    },
    retry: 1,
    select: (data) => {
      return data.data;
    },
  });

쿼리 여러개 부르기 useQueries

useQueries는 React Query에서 제공하는 훅 중 하나이다.
복수의 쿼리를 실행하고 각각의 결과를 처리하는 데 사용

배열 형태로 여러 개의 쿼리를 전달할 수 있다.
각 쿼리는 useQuery와 동일한 방식으로 설정할 수 있다.

import React from 'react';
import { useQueries } from '@tanstack/react-query';
import axios from 'axios';

const Test = () => {
  const ids = [1, 2, 3, 4];

  const fetchPostDetail = (id) => {
    return axios.get(`http://localhost:3004/posts/${id}`);
  };

  const results = useQueries({
    queries: ids.map((id) => {
      return {
        queryKey: ['posts', id],
        queryFn: () => fetchPostDetail(id),
      };
    }),
    combine: (results) => {
      return {
        data: results.map((result) => result.data),
      };
    },
  });
  console.log('테스트결과', results);
  return <div></div>;
};

export default Test;

++ 예를 들어, 두 개의 서로 다른 엔드포인트에서 데이터를 가져와야 하는 경우

import { useQueries } from 'react-query';

function App() {
  const queries = useQueries([
    { queryKey: 'data1', queryFn: () => fetchData('endpoint1') },
    { queryKey: 'data2', queryFn: () => fetchData('endpoint2') },
  ]);

  return (
    <div>
      {queries.map(({ data, isLoading }) => (
        <div key={data.queryKey}>
          {isLoading ? (
            <div>Loading...</div>
          ) : (
            <div>{JSON.stringify(data)}</div>
          )}
        </div>
      ))}
    </div>
  );
}

useQueries 훅을 사용하여 두 개의 쿼리를 실행
각 쿼리는 다른 엔드포인트에서 데이터를 가져온다.
queries 배열을 매핑하여 각 쿼리의 결과를 처리하고 있음

useQueries 훅을 사용하면 여러 개의 쿼리를 한 번에 관리하고 각각의 결과를 효율적으로 처리할 수 있게 된다.


useQuery 옵션들 공식 문서 참고
https://tanstack.com/query/latest/docs/framework/react/reference/useQuery

https://velog.io/@kandy1002/React-Query-%ED%91%B9-%EC%B0%8D%EC%96%B4%EB%A8%B9%EA%B8%B0
react query 예시 참고하기
https://tanstack.com/query/latest/docs/framework/react/overview

profile
웹개발자 꿈나무 꾸준함의 힘을 믿습니다 🚶

0개의 댓글