전 챕터에서는 우리가 흔히 사용하는 방식으로 data-fetching
을 구현해보았다. 이번에는 React-Query
를 사용해보자 👍
먼저 패키지를 설치하자.
yarn add react-query
이제 설치를 마쳤으니 React-Query
를 추가해보자. Redux
를 사용해봤으면 스토어를 세팅하는 것과 매우 유사하게 느낄 수 있을 것이다. 아마 React-Query
도 내부적으로 Context API
를 이용하고 있기 때문이 아닐까..
App.js
import { QueryClient, QueryClientProvider } from 'react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<BrowserRouter>
// ... 생략
</BrowserRouter>
</QueryClientProvider>
);
}
export default App;
이렇게 설정함으로서 이제 모든 컴포넌트에서
React-Query
가 제공하는 메서드와 hook들을 사용할 수 있게 된 것이다.
몇 가지 스텝으로 나누어서 React-Query
를 사용해보자.
import axios from 'axios';
import React from 'react';
import { useQuery } from 'react-query';
function RQSuperHeroes() {
useQuery('super-heroes', () => {
return axios.get('http://localhost:4000/superheroes');
});
return <div>RQSuperHeroes</div>;
}
export default RQSuperHeroes;
useQuery
훅을 이용하는데 이 훅은 첫 번째 인자로 문자열을 받는다. 이 문자열은 마치 redux
의 액션 같은 존재인데 나중에 캐싱과 관련하여 중요하니까 작명을 잘 해보도록 하자.
그리고 두 번째 인자로는 콜백함수를 받는데 이는 Promise
를 리턴하도록 되어있다.
import axios from 'axios';
import React from 'react';
import { useQuery } from 'react-query';
function RQSuperHeroes() {
const { isLoading, data } = useQuery('super-heroes', () => {
return axios.get('http://localhost:4000/superheroes');
});
if (isLoading) {
return <h2>Loading....</h2>;
}
return (
<>
<h2>RQ Super Heroes Page</h2>
{data?.data.map((hero) => {
return <div key={hero.name}>{hero.name}</div>;
})}
</>
);
}
export default RQSuperHeroes;
useQuery
를 통해 받아온 데이터를 변수에 저장해서 렌더링 해주자.
와우.. Promise
로 리턴되는 값을 디스트럭처링 해보면 isLoading, Error, data
등이 자동완성으로 완성되는 것을 확인할 수 있다. 타입스크립트 👍
단순하게 이전 코드와 비교만 해봐도 useEffect
와 useState
같은 훅을 사용하지 않아서 깔끔해졌다.
너무 간단하군...
function SuperHeroesPage() {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState([]);
const [error, setError] = useState('');
useEffect(() => {
axios
.get('http://localhost:4000/superheroes')
.then((res) => {
setData(res.data);
setIsLoading(false);
})
.catch((error) => {
setError(error.message);
setIsLoading(false);
});
}, []);
if (isLoading) {
return <h2>Loading...</h2>;
}
if (error) {
return <h2>{error}</h2>;
}
return (
<>
<h2>Super Heroes Page</h2>
{data.map((hero) => {
return <div key={hero.name}>{hero.name}</div>;
})}
</>
);
}
전통적인 방식으로 에러 핸들링할 때는 다음과 같이 했다. axios
에 대해 catch
메서드를 이용하는 방법이다.
이번엔 리액트쿼리로 에러핸들링을 해보자.
const fetchSuperHeroes = () => {
return axios.get('http://localhost:4000/superheroes11');
};
function RQSuperHeroes() {
const { isLoading, data, isError, error } = useQuery(
'super-heroes',
fetchSuperHeroes,
);
if (isLoading) {
return <h2>Loading....</h2>;
}
if (isError) {
return <h2>{error.message}</h2>;
}
return (
<>
<h2>RQ Super Heroes Page</h2>
{data?.data.map((hero) => {
return <div key={hero.name}>{hero.name}</div>;
})}
</>
);
}
export default RQSuperHeroes;
useQuery
의 리턴으로 isError
와 error
프로퍼티가 반환되므로 이를 통해 간단하게 할 수 있다.
이거 개사기아님..?
React-Query
는 리덕스처럼 편리한 Devtools를 제공한다. 이 편리한 기능으로 디버깅과 데이터들을 한눈에 편하게 볼 수 있다.
App.js
에서 import { ReactQueryDevtools } from 'react-query/devtools';
을 추가해준다.
그리고 가장 아래쪽에서
<ReactQueryDevtools initialIsOpen={false} position="bottom-right" />
다음 코드를 추가해준다.
bottom-right
에 리액트 쿼리 로고가 추가되었다. 클릭해보면 데브툴이 나온다!
/Home
을 클릭했다가 /RQSuperHeroes
에 들어가면 처음에 로딩이 나오고 그 다음에 데이터 fetching이 일어난 것을 확인할 수 있다.
그 이후에 다시 동일 동작을 반복해보면 이번에은 Loading
이 나오지 않음을 확인할 수 있다.
리액트 쿼리는 모든 쿼리 결과에 대한 값을 디폴트로 5분동안 저장하고 있기 때문이다.
흔히 isLoading
프로퍼티와 isFetching
프로퍼티를 비교한다.
isLoading : 데이터가 아무것도 없을 때 처음으로 데이터를 패칭할 때 유용하다.
isFetching : 리패치를 해야할 때 유용하다. 즉, 캐시가 있는 상태에서 backgroud refeching 할 때
const { isLoading, data, isError, error, isFetching } = useQuery(
'super-heroes',
fetchSuperHeroes,
{ cacheTime: 1000 },
);
useQuery의 세 번째 인자로 얼마동안 캐시를 저장할건지를 설정해줄 수 있다. 1000ms로 설정했으므로 1초마다 다시 data-feching을 수행한다.
home으로 이동했다가 1초뒤에 다시 RQSuperHeroes로 이동하면 로딩이 다시 보이는걸 확인할 수 있다.
우리는 staleTime
이라는 프로퍼티로 refetching 횟수를 최소화할 수 있다.
staleTime
을 설정하지 않으면 백그라운드에서 계속해서 리패칭이 일어나게 된다. 네트워크에 불필요한 요청을 보내기 때문에 성능이슈가 생길 수 있다 😥
const { isLoading, data, isError, error, isFetching } = useQuery(
'super-heroes',
fetchSuperHeroes,
{ staleTime: 30000 },
);
이렇게 설정해주면 30초가 지날 때 마다 flag는 fresh
에서 stale
로 변하면서 background refetching이 일어나게 된다.
여담으로 stale은 한국말로 신선하지 않은 이라는 형용사이다. 즉 처음 받은 데이터는 fresh 하지만 30초가 지난 데이터는 썩었다는 것이다 💩
const { isLoading, data, isError, error, isFetching } = useQuery(
'super-heroes',
fetchSuperHeroes,
{ refetchOnMount: true },
);
컴포넌트가 마운트되었을 때 리패치할것이냐에 대한 프로퍼티이다. useEffect
로 구현했을 때와 비슷한 느낌으로 사용할 수 있다. 만약 false로 변경하면 onMount일 때마다 리패치를 하지 않는다.
const { isLoading, data, isError, error, isFetching } = useQuery(
'super-heroes',
fetchSuperHeroes,
{ refetchOnWindowFocus: true },
);
default로 true
로 설정이 되어있는데 윈도우에 포커스가되면 리패치를 한다는 옵션이다.
const { isLoading, data, isError, error, isFetching } = useQuery(
'super-heroes',
fetchSuperHeroes,
{ refetchInterval: false },
);
기본값은 false로 초기화되어 있는데 밀리세컨드를 줄 수 있다.
const { isLoading, data, isError, error, isFetching } = useQuery(
'super-heroes',
fetchSuperHeroes,
{ refetchInterval: 2000 },
);
2초 마다 fresh와 stale로 변경되면서 계속해서 리패치가 일어남을 알 수 있다.
const { isLoading, data, isError, error, isFetching } = useQuery(
'super-heroes',
fetchSuperHeroes,
{ refetchInterval: false, refetchIntervalInBackground: true },
);
refetchIntervalInBackground
옵션을 true
로 주면 윈도우 포커스가 없어도 계속해서 데이터가 리패칭된다.