ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React Query] (2) useQuery()
    Front-End(Web)/React - 라이브러리들 2022. 7. 26. 21:14
    반응형

    🧐 서론

    첫 번째 포스팅에서는 React Query를 도입하는 배경과 장점, 그리고 기본개념들을 우선 정리하였다.

    그리고, 이번 포스팅부터는 실질적으로 다루기 위한 문법들을 정리하려고 한다.

     

    React Query에는 주요 개념들이 있었으며, 이번 주제는 비동기 데이터를 지칭하는 쿼리(Queries)를 다루는 문법들이 될 것이다.

    쿼리 데이터를 패치하는 useQuery() 메서드와 이 데이터를 다루기 위한 다양한 방법들을 정리해보겠다!

     

    * https://www.youtube.com/watch?v=Ev60HKYFM0s&list=PLC3y8-rFHvwjTELCrPrcZlo6blLBUspd2&index=3 

     


     

    🌺 useQuery() : 데이터 패치 Hooks

    우선, 어플리케이션의 컴포넌트 트리 최상단에 설정했던 코드를 다시 리마인드한다.

    // src/index.js
    import React from "react";
    import ReactDOM from "react-dom";
    import App from "./App";
    import { QueryClient, QueryClientProvider } from "react-query";
    import { ReactQueryDevtools } from "react-query/devtools";
    
    const queryClient = new QueryClient();		// 인스턴스 생성
    
    ReactDOM.render(
      <React.StrictMode>
        <QueryClientProvider client={queryClient}>
          {/* devtools */}
          <ReactQueryDevtools initialIsOpen={true} />
          <App />
        </QueryClientProvider>
      </React.StrictMode>,
      document.getElementById("root")
    );

     

    - useQuery() : 데이터 패치

    우선, 기존의 axios를 활용해 서버 데이터를 가져오는 <SuperHeroesPage> 컴포넌트를 예시로 가져왔다.

    useState() 로 데이터 및 로딩여부 상태값을 관리하며, useEffect() 로 마운트를 보장한 뒤 데이터를 호출하는 게 통상적이었다.

    import { useState, useEffect } from 'react'
    import axios from 'axios'
    
    export const SuperHeroesPage = () => {
      const [isLoading, setIsLoading] = useState(true)
      const [data, setData] = useState([])
    
      useEffect(() => {
        axios.get('http://localhost:4000/superheroes').then(res => {
          setData(res.data)
          setIsLoading(false)
        })
      }, [])
    
      if (isLoading) {
        return <h2>Loading...</h2>
      }
    
      return (
        <>
          <h2>Super Heroes Page</h2>
          {data.map(hero => {
            return <div>{hero.name}</div>
          })}
        </>
      )
    }

    이러한 BoilerPlate 들이 많이 발생했다는 것이 기존 작업의 번거로움일 것이다. 이를, React Query로 어떻게 구현할 수 있는지 알아보자!

     

    import { useQuery } from "react-query";
    import axios from "axios";
    
    const fetchSuperHeroes = () => {
      return axios.get("http://localhost:4000/superheroes");
    }
    
    export const RQSuperHeroesPage = () => {
      const { isLoading, data } = useQuery("super-heroes", fetchSuperHeroes);
    
      if (isLoading) return <h2>Loading...!!</h2>;
    
      return (
        <>
          <h2>React Query Super Heroes Page</h2>
          {data?.data.map((hero) => (
            <div key={hero.name}>{hero.name}</div>
          ))}
        </>
      );
    };

    useQuery() Hooks를 통해 데이터를 패치할 수 있다. 이는, API request 중 GET 요청을 할 때 주로 사용되는 Hooks이다.

    인자는 queryKey(고유 키값), queryFn(데이터 패치함수), options(옵션) 3가지를 받는다.

    1. queryKey : 문자열 혹은 문자열의 배열로 할당. 데이터 캐싱에 참조되며, key가 같은 쿼리들은 하나의 요청으로 값을 공유한다.
    2. queryFn : fetcher 즉 Promise 처리하는 함수를 할당. fetch나 axios를 통한 request 함수가 적용된다.
    3. options : 필수가 아니며, 아래와 같은 옵션들을 지정 가능하다.
      • enable(Boolean) : 데이터 자동패치 여부. 기본값은 true.
      • retry(Boolean | Number) : 데이터 재요청 여부 및 횟수. 기본값은 false이며, true의 기본 재요청 횟수는 3회.
      • retryNumber(Number) : 재요청까지 대기시간을 설정 가능. 기본값은 0.
      • staleTime(Number | Infinity) : 데이터가 fresh 상태로 유지되는 시간. 설정시간이 지나면 stale이 되며, 기본값은 0.
      • cacheTime(Number | Infinity) : inactive 상태인 캐시 데이터가 메모리에 남아있는 시간. 해당 시간이 초과되면 가비지 컬렉터에 의해 메모리에서 제거되며, 기본값은 5분. (브라우저의 캐시 비우기와 연관됨)
      • refetchOnMount, refetchOnWindowFocus, refetchOnReconnect(Boolean | "always") : 쿼리가 stale 상태인 경우에 각각 마운트 시, 브라우저 창 포커싱 시, 네트워크 연결 시 refetch 실행여부이다. 기본값은 true.
      • refetchInterval : refetch가 발생하는 간격을 설정. 기본값은 false.
      • onSuccess((data: TData) => void) : 데이터 fetch 성공 시 실행되는 콜백. 매개변수는 response 데이터가 들어있다.
      • onError((error: TError) => void) : 데이터 fetch 실패 시 실행되는 콜백. 매개변수는 Error 정보가 들어있다.
      • onSettled((data?: TData, error?: TError) => void) : 데이터 fetch 완료 시 실행되는 콜백. 성공/실패 여부와 상관없이 실행.
      • 이외에도, select(데이터 형상수정), keepPreviousData(기존 데이터 유지여부), initialData(캐시 초기 데이터 사용) 등 옵션들이 있다.

     

    아래와 같은 반환값들이 있다.

    • data : 요청 성공 시 응답받은 데이터
    • error : 요청 실패 시 에러정보
    • refetch : 수동으로 데이터 refetch를 실행하는 함수이다. stale이나 cache 설정을 무시하고 무조건 데이터를 refetch한다.
    • status : idle(초기상태) / loading(fetch 중) / error(fetch 실패) / success(fetch 성공)
    • 그 외, 상태값 Boolean인 isLoading(API 재호출 + 캐시저장), isFetching(API 재호출), isIdle, isSuccess, isError, isStale

     

     

    * Error Handing(에러 핸들링)

     

    마찬가지로 기존 예시를 먼저 가져왔다. error 상태값 추가, catch 문으로 핸들링 등 추가 공수가 들게 된다.

    export const SuperHeroesPage = () => {
      const [isLoading, setIsLoading] = useState(true);
      const [data, setData] = useState([]);
      // 1) Error 메세지 상태값 추가
      const [error, setError] = useState("");
    
      useEffect(() => {
        axios
          .get("http://localhost:4000/superheroes")
          .then((res) => {
            setData(res.data);
            setIsLoading(false);
          })
          // 2) catch문으로 패치 시 Error 처리 추가
          .catch((err) => {
            setError(err.message);
            setIsLoading(false);
          });
      }, []);
    
      if (isLoading) return <h2>Loading...</h2>;
    
      // 3) Error UI 분기
      if (error) return <h3>{error}</h3>;
    
      return (
        <>
          <h2>Super Heroes Page</h2>
          {data.map((hero) => {
            return <div>{hero.name}</div>;
          })}
        </>
      );
    };

     

    이를, useQuery() 에서 제공하는 상태값들로 처리할 경우 더욱 간단하게 구현할 수 있다.

    import { useQuery } from "react-query";
    import axios from "axios";
    
    const fetchSuperHeroes = () => {
      return axios.get("http://localhost:4000/superheroes");
    };
    
    export const RQSuperHeroesPage = () => {
      // 1) + 2)
      const { isLoading, data, isError, error } = useQuery(
        "super-heroes",
        fetchSuperHeroes
      );
    
      if (isLoading) return <h2>Loading...!!</h2>;
    
      // 3)
      if (isError) return <h2>{error.message}</h2>;
    
      return (
        <>
          <h2>React Query Super Heroes Page</h2>
          {data?.data.map((hero) => (
            <div key={hero.name}>{hero.name}</div>
          ))}
        </>
      );
    };

    에러발생 여부인 isError, 에러내용인 error 두 가지 상태값을 useQuery() 에서 추가로 가져왔다.

    별도의 상태값 추가나 catch문 처리 없이 useQuery() Hooks로 일괄적으로 다룰 수 있다는 장점이 와닿았다.

     


     

    🌺 useQuery() 의 다양한 기능들

     

    1. 데이터 캐싱 : staleTime vs cacheTime

    React Query는 데이터가 만료(stale)되지 않았다면, 구지 재요청하지 않고 캐시된 데이터를 활용한다.

    쿼리는 불러온 뒤 기본적으로 만료(stale) 상태가 되지만, staleTime과 cacheTime 옵션을 부여해서 데이터 캐싱을 설정할 수 있다.

    import { useQuery } from "react-query";
    import axios from "axios";
    
    const fetchSuperHeroes = () => {
      return axios.get("http://localhost:4000/superheroes");
    };
    
    export const RQSuperHeroesPage = () => {
      const { isLoading, data, isError, error, isFetching } = useQuery(	// isFetching 상태값 추가
        "super-heroes",
        fetchSuperHeroes,
        {
          staleTime: 30000,	// fresh 유지시간 옵션 추가 (default 0초)
          cacheTime: 30000,	// 캐싱시간 옵션 추가 (default 5분)
        },	
      );
    
      if (isLoading) return <h2>Loading...!!</h2>;
    
      if (isError) return <h2>{error.message}</h2>;
    
      return (
        <>
          <h2>React Query Super Heroes Page</h2>
          {data?.data.map((hero) => (
            <div key={hero.name}>{hero.name}</div>
          ))}
        </>
      );
    };

    위에서 봤다시피, useQuery() 의 세 번째 인자로 옵션 객체를 전달하는데, 여기서 staleTime, cacheTime을 설정할 수 있다.

    두 가지 중요하고 유사한 개념들을 잘 구분하여야 캐싱기능을 적절히 활용할 수 있다.

     

    1) staleTime

    • 데이터가 fresh -> stale 상태로 변경되는데 걸리는 시간. 기본값은 0초.
    • fresh 상태일 때 쿼리 인스턴스가 새롭게 mount 되어도 fetch 미발생. 또한, unmount 후 mount 되도 fetch 미발생.
    • 데이터가 fresh한 상태이어야 캐싱 데이터를 활용할 수 있다.

    2) cacheTime

    • 데이터가 inactive(쿼리 unmount 이후) 상태일 때 캐싱된 상태로 남아있는 시간. 기본값은 5분.
    • cacheTime이 지나면 가비지 콜렉터로 수집된다. 브라우저의 캐시 삭제와 관련이 있음.
    • cacheTime이 지나기 전에 쿼리 인스턴스가 mount 되면, 데이터를 fetch하는 동안 캐시 데이터를 보여준다.

    그렇기에, staleTime을 설정하지 않으면 데이터는 캐싱되나, 항상 stale 상태를 가지기에 refetch가 계속해서 발생하게 된다.

    또한, cacheTime이 staleTime보다 짧다면 fresh 상태동안 캐싱이 되지 않을 것이므로 두 값 모두를 적절히 설정해줘야 하는 것이다.

     

    캐싱이 된 상태에서 fetch가 일어난다면, isLoading은 false, isFetching은 true 값을 각각 반환한다.

    • isFetching : 데이터가 fetch 될 때 true. 캐싱 데이터가 있어 백그라운드에서 fetch하더라도 true.
    • isLoading : 캐싱된 데이터가 없을 시 fetch 될 때 true.

     

     

    2. Update(1) : polling 방식

    쿼리를 주기적으로 최신화하기 위한 Polling 기능도 적용할 수 있다. 이는, 옵션의 refetchInterval 등 설정으로 가능하다.

      const { isLoading, data, isError, error } = useQuery(
        "super-heroes",
        fetchSuperHeroes,
        {
          refetchInterval: 2000,			// refetch 주기
          refetchIntervalInBackground: true,	// 윈도우 focus 아니어도 refetch
        }
      );

    위처럼, 주기를 설정하는 refetchInterval, 백그라운드에서도 실행하는 refetchIntervalInBackground 2개 옵션을 설정할 수 있다. 

     

     

    3. Update(2) : refetching 방식

    쿼리의 기본 업데이트 원리나 Polling 방식이 아닌, 특정 동작(클릭 이벤트 등)에서 패치를 적용하는 방법도 있다.

      // 1) refetch 함수 가져오기
      const { isLoading, data, isError, error, refetch } = useQuery(
        "super-heroes",
        fetchSuperHeroes,
        {
          enabled: false,	// 2) 기본동작 비활성화
        }
      );
    
      return (
        <>
          <h2>React Query Super Heroes Page</h2>
          <button onClick={refetch}>refresh</button>	// 3) 버튼 추가
          {data?.data.map((hero) => (
            <div key={hero.name}>{hero.name}</div>
          ))}
        </>
      );

    우선, enabled 옵션을 주게 되면 stale 상태에서의 자동 패칭이 비활성화된다.

    그리고, useQuery()의 refetch 메서드를 가져온 뒤, 버튼의 onClick 이벤트 핸들러로 적용하면 된다.

     

     

    4. Success & Error Callback

      const onSuccess = (data) => {
        console.log("Data Fetching Success!", data);
      };
    
      const onError = (error) => {
        console.log("Data Fetching Fail..", error);
      };
    
      const { isLoading, data, isError, error, refetch } = useQuery(
        "super-heroes",
        fetchSuperHeroes,
        {
          onSuccess,
          onError,
        }
      );

    데이터 패칭에 성공 및 실패했을 때 각각 onSuccess, onError 옵션을 통해 실행할 콜백함수를 설정할 수 있다.

    각 함수는 성공 시 불러온 data, 실패 시 에러정보를 인자로 받을 수 있다. (에러의 경우, 기본 옵션인 3회 요청 후 실패 시 발생)

     

     

    5. Data Transformation

      const { isLoading, data, isError, error, refetch } = useQuery(
        "super-heroes",
        fetchSuperHeroes,
        {
          // select() - Data를 변형시키는 옵션 메서드
          select: (data) => {
            return data.data.map((hero) => hero.name);
          },
        }
      );
    
      if (isLoading) return <h2>Loading...!!</h2>;
    
      if (isError) return <h2>{error.message}</h2>;
    
      return (
        <>
          <h2>React Query Super Heroes Page</h2>
          <button onClick={refetch}>refresh</button>
          // 변형된 data를 활용!
          {data.map((name) => (
            <div key={name}>{name}</div>
          ))}
        </>
      );

    보통 API로 불러온 데이터를 그대로 사용하기보다는, 필요한 내용 혹은 계산된 값으로 변형해서 사용하는 경우가 많다.

    이를 적용하는 옵션이 바로 select 이다. 이 옵션은 콜백함수로, 성공 시 받아온 data를 인자로 받아 변형된 형상을 반환해야한다.

     


    React Query에서 GET을 담당하는 useQuery() 와 이를 사용하는 데 알면 좋을 기능과 옵션들에 대해 알아보았다.

     

    확실히 데이터를 불러오는 데 활용되는 다양한 정보나 기능을 지원하고, 특히 캐싱과 업데이트까지 커스텀할 수 있다는 부분이 매우 유용하다고 느껴졌다.

     

    다음 강의는, Query Hook을 커스텀해서 사용하는 내용, 그리고 다양한 쿼리(페이지네이션, 인피니트 스크롤 등) 기능들에 대해 심도있게 알아보는 포스팅이 될 것으로 예상된다!

     

    📎 출처

    - [공식] React Query 공식문서 : https://react-query-v3.tanstack.com/overview  

    - [강의] Codevolution 유튜브 강의 : https://www.youtube.com/watch?v=Ev60HKYFM0s&list=PLC3y8-rFHvwjTELCrPrcZlo6blLBUspd2&index=3 

     

    - [React Query 개념과 예제] DevKkiri 님의 블로그 : https://devkkiri.com/post/f14703ea-a105-46e2-89e8-26282de36a3a  

     

    - [useQuery 기본] jfori 님의 블로그 : https://jforj.tistory.com/243

    - [useQuery Options] tanStack : https://tanstack.com/query/v4/docs/reference/useQuery?from=reactQueryV3&original=https://react-query-v3.tanstack.com/reference/useQuery

    - [staleTime과 cacheTime] yrnana 님의 블로그 : https://velog.io/@yrnana/React-Query%EC%97%90%EC%84%9C-staleTime%EA%B3%BC-cacheTime%EC%9D%98-%EC%B0%A8%EC%9D%B4  

    - [refetchInterval(Polling)] rlawogks2468 님의 블로그 : https://velog.io/@rlawogks2468/React-Query  

     

    반응형
Designed by Tistory.