공식 문서 참고
https://tanstack.com/query/latest
현재 시점은 5버전 !
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 훅은 데이터 조회 작업에 사용되며
하나의 매개변수를 갖는데 이 매개변수는 객체타입이다.
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초 후에 캐시가 비워지는 걸 확인할 수 있다.
리액트 쿼리는 데이터의 상태에 대해 유통기한을 나누게 된다.
Fetching(api가 호출되고 있을 때) - > Fresh(막 데이터가 도착했을 때) - >
Stale(데이터가 도착한 후 시간이 지났을 때)-> Inactive(캐시를 사용하지 않는 상태/ 캐시삭제 카운트 들어감)
리액트 쿼리의 장점중 하나가
fresh한 상태인 api는 다시 호출하지 않게 통제하고 데이터가 유통기한이 끝난 stale 상태일 때 다시 api 호출을 하도록 조절할 수 있다는 점이다.
이러면 기대되는 효과는 예를 들어 한 번 api호출로 받아온 데이터중 잘 변하지 않는 값들인 경우엔 여러번 호출을 계속 할 필요가 없을 때 fresh 상태로 길게 유지되게 해주면 불필요한 요청을 줄일 수 있게 된다.
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
옵션은 데이터를 주기적으로 다시 가져오는 간격(초)을 설정한다.
테스트로 3초 설정 - > 3초마다 api를 호출하는 것을 확인할 수 있다.
refetchOnMount: false
옵션을 사용하면 컴포넌트가 마운트될 때 데이터를 다시 가져오지 않도록 설정한다.
페이지가 로드될 때 초기 데이터를 미리 가져온 후
사용자가 상호 작용하는 동안 데이터를 업데이트하지 않아야 할 때 사용할 수 있다.
초기 데이터 로드가 빈번하게 변경되지 않는 경우에도 리소스를 절약할 수 있음
원하는 경우에만 데이터를 다시 가져올 수 있게 조절할 수 있게 된다.
기본값은 true이다.
refetchOnWindowFocus: true
옵션은 사용자가 브라우저 창에 다시 포커스될 때 데이터를 자동으로 다시 가져오도록 설정한다.
이는 사용자가 애플리케이션 창을 다시 활성화할 때 최신 데이터를 가져와 사용자 경험을 향상시키는 데 유용하다.
사용자가 다른 탭으로 전환한 후 다시 애플리케이션으로 돌아올 때마다 데이터를 다시 가져오는 것이 사용자에게 부담이 될 경우도 있으므로 이 옵션을 사용할 때는 사용자 경험을 고려하여 신중하게 설정해야 한다.
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는 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