React-Query 튜토리얼(2)

오호·2022년 2월 25일
8
post-thumbnail
post-custom-banner

전 챕터에서는 우리가 흔히 사용하는 방식으로 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들을 사용할 수 있게 된 것이다.

2. RQ data fetching

몇 가지 스텝으로 나누어서 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등이 자동완성으로 완성되는 것을 확인할 수 있다. 타입스크립트 👍

단순하게 이전 코드와 비교만 해봐도 useEffectuseState같은 훅을 사용하지 않아서 깔끔해졌다.

너무 간단하군...

3. 에러 핸들링

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의 리턴으로 isErrorerror프로퍼티가 반환되므로 이를 통해 간단하게 할 수 있다.
이거 개사기아님..?

4. devtools

React-Query는 리덕스처럼 편리한 Devtools를 제공한다. 이 편리한 기능으로 디버깅과 데이터들을 한눈에 편하게 볼 수 있다.

App.js에서 import { ReactQueryDevtools } from 'react-query/devtools';을 추가해준다.
그리고 가장 아래쪽에서

<ReactQueryDevtools initialIsOpen={false} position="bottom-right" />

다음 코드를 추가해준다.

bottom-right에 리액트 쿼리 로고가 추가되었다. 클릭해보면 데브툴이 나온다!

5. cache

5.1 isFetching vs isLoading

/Home을 클릭했다가 /RQSuperHeroes에 들어가면 처음에 로딩이 나오고 그 다음에 데이터 fetching이 일어난 것을 확인할 수 있다.

그 이후에 다시 동일 동작을 반복해보면 이번에은 Loading이 나오지 않음을 확인할 수 있다.

리액트 쿼리는 모든 쿼리 결과에 대한 값을 디폴트로 5분동안 저장하고 있기 때문이다.

흔히 isLoading 프로퍼티와 isFetching프로퍼티를 비교한다.

  • isLoading : 데이터가 아무것도 없을 때 처음으로 데이터를 패칭할 때 유용하다.

  • isFetching : 리패치를 해야할 때 유용하다. 즉, 캐시가 있는 상태에서 backgroud refeching 할 때

5.2 cache time

const { isLoading, data, isError, error, isFetching } = useQuery(
    'super-heroes',
    fetchSuperHeroes,
    { cacheTime: 1000 },
  );

useQuery의 세 번째 인자로 얼마동안 캐시를 저장할건지를 설정해줄 수 있다. 1000ms로 설정했으므로 1초마다 다시 data-feching을 수행한다.

home으로 이동했다가 1초뒤에 다시 RQSuperHeroes로 이동하면 로딩이 다시 보이는걸 확인할 수 있다.

6. Stale Time

우리는 staleTime이라는 프로퍼티로 refetching 횟수를 최소화할 수 있다.

staleTime을 설정하지 않으면 백그라운드에서 계속해서 리패칭이 일어나게 된다. 네트워크에 불필요한 요청을 보내기 때문에 성능이슈가 생길 수 있다 😥

 const { isLoading, data, isError, error, isFetching } = useQuery(
    'super-heroes',
    fetchSuperHeroes,
    { staleTime: 30000 },
  );

이렇게 설정해주면 30초가 지날 때 마다 flag는 fresh에서 stale로 변하면서 background refetching이 일어나게 된다.

여담으로 stale은 한국말로 신선하지 않은 이라는 형용사이다. 즉 처음 받은 데이터는 fresh 하지만 30초가 지난 데이터는 썩었다는 것이다 💩

7. refetch

7.1 refetchOnMount

const { isLoading, data, isError, error, isFetching } = useQuery(
    'super-heroes',
    fetchSuperHeroes,
    { refetchOnMount: true },
  );

컴포넌트가 마운트되었을 때 리패치할것이냐에 대한 프로퍼티이다. useEffect로 구현했을 때와 비슷한 느낌으로 사용할 수 있다. 만약 false로 변경하면 onMount일 때마다 리패치를 하지 않는다.

7.2 refetchOnWindowFocus

 const { isLoading, data, isError, error, isFetching } = useQuery(
    'super-heroes',
    fetchSuperHeroes,
    { refetchOnWindowFocus: true },
  );

default로 true로 설정이 되어있는데 윈도우에 포커스가되면 리패치를 한다는 옵션이다.

8. polling

 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로 주면 윈도우 포커스가 없어도 계속해서 데이터가 리패칭된다.

profile
오호
post-custom-banner

0개의 댓글