React Query 사용하기

유환익·2022년 2월 1일
0

새로운 기술을 도입하기 전에는, 이 기술이 어떤 문제를 해결하려고 하는 지에 대해 먼저 파악하는 것이 중요하다는 멘토님의 말씀을 따라 먼저 React Query를 사용하지 않은 기존의 Data Fetching 방식으로 연습을 진행했다.

/* db.json */
{
  "superheroes": [
    {
      "id": 1,
      "name": "Batman",
      "alterEgo": "Bruce Wayne"
    },
    {
      "id": 2,
      "name": "Superman",
      "alterEgo": "Clark Kent"
    },
    {
      "id": 3,
      "name": "Wonder Woman",
      "alterEgo": "Princess Diana"
    }
  ]
}

해당 JSON 데이터는 json-server 라이브러리를 통해 서버화한 파일이다. 로컬에서 임의로 만든 파일이지만, 서버로 내보내어 비동기 요청을 보낼 수 있게 하였다. 해당 작업은, package.json의 scripts 란에

"serve-json": "json-server --watch db.json --port 4000"

를 추가하여 명령어로 4000번 포트로 돌리면 해당 서버가 돌아갈 수 있도록 하였다.

import axios from 'axios';
/* 
단순히 서버에서 데이터를 요청하고 저장하는 것임에도
두가지 훅을 받아와 사용해야 한다.
*/
import { useEffect, useState } from 'react';

export const SuperHeroesPage = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [data, setData] = useState([]);

  useEffect(() => {
    const fetchSuperHeroes = async () => {
      try {
        const response = await axios.get('http://localhost:4000/superheroes');
        const data = await response.data;
        setData(data);
        setIsLoading(false);
      } catch (error) {
        setError(error.message);
        setIsLoading(false);
      }
    };
    fetchSuperHeroes();
  }, []);
  
  /* isLoading 중이라면, 서버 데이터를 사용하는 로직 대신, Loading... 메시지를 보여준다. */
  if (isLoading) {
    return <h2>Loading...</h2>;
  }
  
  /* 에러가 발생하면, 상태값에 저장한 에러메시지를 보여준다. */
  if (error) {
   	return <h2>{error}</h2>
  }
  return (
    <div>
      <h2>Super Heroes Page</h2>
      {data.map((hero) => (
        <div key={hero.name}>{hero.name}</div>
      ))}
    </div>
  );
};

재래적인 방식의 data fetching 로직이다.

단점은 아래와 같다.

  • 단순히 서버에서 데이터를 받아오는 것임에도 불구하고 많은 코드를 작성해야 한다.
  • useEffect 훅으로 데이터를 비동기적을 요청하고, useState 훅으로 data fetching 중 loading 상태, 그리고 결과 데이터를 저장 및 관리해야 하니, 복잡한 게 눈에 띈다.
  • 또한, 데이터를 받아오는 도중 상태를 나타내기 위한 isLoading state 또한 useState로 따로 선언해야 하니 번거롭다.

아래는 이번에 학습한 React Query 훅을 사용해 Data Fetching 작업을 해보았다.

import axios from 'axios';
import { useQuery } from 'react-query';

export const RQSuperHeroesPage = () => {
  /* 

  */
  const { data, isLoading, isError, error } = useQuery('super-heroes', () => {
    return axios.get('http://localhost:4000/superheroes');
  });

  if (isLoading) {
    return <h2>Loading...</h2>;
  }
  /* 에러가 발생하면 에러메시지를 보여주지만,
   React Query 라이브러리가 자동으로 다시 fetching을 시도하기 때문에 로딩 상태가 더 길게 지속된다.
  */
  if (isError) {
    return <h2>{error.message}</h2>;
  }

  return (
    <div>
      <h2>RQ Super Heroes Page</h2>
      {data?.data.map((hero) => (
        <div key={hero.name}>{hero.name}</div>
      ))}
    </div>
  );
};
  • useQuery 훅을 사용하니, data fetching을 하기 위한 코드가 대폭 줄었다.
  • .then() 혹은 await을 수반하는 복잡한 Promise 정제 작업 또한 사라졌다.
  • useQuery 훅의 리턴값에는 loading state 혹은 error state 또한 포함되므로, 값은 distructuring을 통해 따로 뽑아 사용할 수 있으니, 훨씬 편리하며 정확하다.
  • fetching이 실패하면, 자동으로 React Query 라이브러리가 재시도한다.

더 놀라운 점은, 기존의 useEffect 훅을 사용한 비동기 호출과 비교해서, React Query가 query cache 기능을 추가로 제공하기 때문에, 같은 데이터를 여러번 호출할 때에도 이미 캐싱된 데이터를 보여준다.

기본적으로 React Query로 수행한 모든 query fetching의 결과는, 5분간 cache를 지속하기 때문에 이러한 결과가 나오는 것이다.
(5분 이후에는 query cache는 가비지 컬렉팅 된다. 따라서 다시 loading state는 true로 바뀐다.)

  • 5분의 default setting된 시간을 임의로 바꾸려면, useQuery 훅의 세번째 인자로 {cacheTime: 5000(밀리초)}를 넣어 변경할 수 있다.

따라서 로딩 페이지가 한번 API콜을 수행한 이후에는 보여지지 않아 성능의 향상을 보여준다.

해당 작업이 이루어지는 과정은 다음과 같다.

useQuery문이 인자로 넣어준 super-heroes key를 통해 최초로 실행되면 isLoading state가 true로 설정되며, 요청이 데이터를 받아오기 위해 보내진다.

요청이 완료되면, key로 설정한 "super-heroes"와 fetchSuperHeroes 함수를 unique identifier로 활용하여 cache 된다.

이후에 다시 같은 query call을 하면, react query는 cache 상에 해당 uniquer identifier가 존재하는 지 확인하고
존재한다면, isLoading state를 true로 설정하지 않고 해당 cache 된 데이터를 보여준다.

그러나, cache 된 데이터가 최신 데이터가 아닐 가능성도 있으므로, 같은 query 요청에 대해 React Query는 background refetch가 trigger되면서 새로 갱신된 데이터가 UI 화면 상에 업데이트도 해준다..!

cache 된 데이터가 재차 요청한 데이터와 동일하다면 UI 화면 상의 데이터는 그대로 유지된다.

여기서, background refetching이 실행될 때 사용되는 flag state는 isFetching이다.

이와 같은 작업이 수행되면서, DB 상의 데이터가 변경되면 기존 client단에서는 isLoading state는 false로 유지하되 isFetching이 true로 바뀌어 cache된 데이터를 UI 화면 상에 보여주다가, isFetching 값이 false가 되면 새로 업데이트된 데이터를 Loading 화면없이 업데이트 하여 보여주는 것이 가능하게 되는 것이다.

결과적으로 페이지 유저는 최초의 페이지 로딩을 제외하면, query call이 성공적으로 마무리 될 경우, 다시 로딩 페이지를 볼 일이 없어지는 것이다.


/* 
인라인으로 작성했던 비동기 요청 함수를 다음과 같이 빼줄 수도 있다.
더 직관적인 코딩이 가능하다.
*/
const fetchSuperHeroes = () => {
  return axios.get('http://localhost:4000/superheroes');
};

export const RQSuperHeroesPage = () => {
  
  const { data, isLoading, isError, error, isFetching } = useQuery(
    'super-heroes',
    fetchSuperHeroes
  );

다음 포스트에서는 React Query에서 제공하는 강력한 툴인 React Query DevTools 에 대해 다루어 보겠다.

profile
사용자의 편의를 더 생각하고 편안한 UI/UX 개발을 꿈꾸는 프론트엔드 개발자 지망생입니다.

0개의 댓글