[React] React Query

AI 개발자 웅이·2022년 6월 12일
0

React

목록 보기
3/9

React query는 서버로부터 비동기로 데이터를 조회할 때 사용된다. React query를 사용하면 기존에 isLoading, isError, refetch, 데이터 캐싱 등 개발자가 직접 구현하려면 꽤 귀찮거나 까다로웠던 기능을 제공해주기 때문에, 서버 상태를 매우 효율적으로 관리할 수 있다.

먼저, 아래 명령어로 react-query를 install해주자.

npm i --save react-query

왜 react query를 사용하는 것이 서버 상태를 효율적으로 관리할 수 있는 것일까?
아래 두 예시를 살펴보자.


React query를 사용하지 않는 경우

const [coins, setCoins] = useState<CoinInterface[]>([]);
const [loading, setLoading] = useState(true);
  useEffect(() => {
    (async () => {
      const response = await fetch("https://api.coinpaprika.com/v1/coins");
      const data = await response.json();
      setCoins(data)
      setLoading(false);
    })();
  }, []);

React query를 사용하는 경우

const { isLoading, data: coins } = useQuery("allCoins", async () => 
(await fetch("https://api.coinpaprika.com/v1/coins")).json();

useState
useEffect


위의 예시에서 알 수 있듯이 isLoading같은 서버 상태 관리 기능을 하나씩 직접 구현하는 것은 매우 번거러운 일이며, react query를 이용한다면 훨씬 쉽게 해당 기능들을 사용할 수 있다.

QueryClientProvider, QueryClient

React query를 사용하기 위해서는 QueryClient 인스턴스와 QueryClientProvider로 기본 세팅을 해줄 필요가 있다.

  • QueryClient 인스턴스를 생성해준다.
  • QueryClientProvider로 컴포넌트의 최상단을 감싸준다.
  • QueryClientProcider의 client에 QueryClient 인스턴스를 입력해준다.
import {QueryClient, QueryClientProvider} from "react-query";

const queryClient = new QueryClient();

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement);
  
root.render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>
);

useQuery

useQuery는 서버로부터 받은 데이터와 서버 상태에 대한 정보를 얻을 수 있는 custom hook으로 이를 구현하기 위해서는 다음 두 가지 개념을 이해하고 있어야 한다.

  • queryKey
  • queryFn

또한, useQuery는 아래와 같이 사용한다.

(1)
const res = useQuery(queryKey, queryFn);

(2)
const res = useQuery({
    queryKey: queryKey,
    queryFn: queryFn
});

queryKey

queryKey는 useQuery마다 할당되는 고유 key이며, useQuery는 queryKey를 기반으로 데이터 캐싱을 관리할 수 있게 도와준다. Key 값은 문자열 또는 배열 형태로 입력할 수 있다.

// (1)
const res = useQuery('coins', queryFn);

// (2)
const res = useQuery(['coins'], queryFn);

// (3)
const res = useQuery(['coins', 'addId'], queryFn);

// (4)
const res = useQuery(['addId', 'coins'], queryFn);

// (5)
const res = useQuery(['coins', {type: 'add', name: 'Id'}], queryFn);

문자열로 key를 입력하면, useQuery에서 길이가 1인 배열로 인식하므로 (1)과 (2)는 결국 같은 key를 입력한 것이다. 또한 useQuery에서는 key 입력 시 배열의 순서도 고려하므로, (3)과 (4)는 다른 key를 입력한 것이다.

위에서 언급한 것처럼 queryKey는 데이터 캐싱을 관리할 수 있게 도와준다. 아래 예시를 살펴보자.

import { useQuery } from 'react-query';

function App() {
    const getCoins1 = () => {
        const res1 = useQuery(['coins'], queryFn1);
    }
    
    const getCoins2 = () => {
        const res2 = useQuery(['coins'], queryFn2);
    }

    return (
        <div>
            {getCoins1()}
            {getCoins2()}
        </div>
    )
}

export default App;

res1과 res2에서는 똑같은 queryKey를 사용하여 서버로부터 데이터 조회 요청을 하고 있다. 일반적으로 queryKey가 다른 상황이라면 두 가지 요청을 모두 전달하겠지만, res1과 res2의 경우 같은 queryKey를 사용하고 있기 때문에 queryFn1에 대한 요청만 전달된다. 왜냐하면 res1에서 요청을 서버에 전달하게 되면 res2에서는 이미 동일한 queryKey에 대한 결괏값이 있기 때문에 추가 요청을 하지 않고 res1의 결과를 그대로 가져와 사용하기 때문이다. 따라서, 아래 코드와 같은 결과를 얻는다.

import { useQuery } from 'react-query';

function App() {
    const getCoins1 = () => {
        const res1 = useQuery(['coins'], queryFn1);
    }
    
    const getCoins2 = () => {
        const res2 = useQuery(['coins']);
    }

    return (
        <div>
            {getCoins1()}
            {getCoins2()}
        </div>
    )
}

export default App;

queryFn

queryFn(query function)은 서버로부터 api 데이터를 요청하는 함수로, promise 처리가 이루어지는 함수라고 보면 된다.

아래와 같은 형태로 작성한다.

import { useQuery } from 'react-query';

const BASE_URL = "https://api.coinpaprika.com/v1";

async function fetchCoins() {
  return (await fetch(`${BASE_URL}/coins`)).json();
}

interface CoinInterface {
  id: string;
  name: string;
  symbol: string;
  rank: number;
  is_new: boolean;
  is_active: boolean;
  type: string;
}

const { isLoading, data } = useQuery<CoinInterface[]>(['allCoins'], fetchCoins);

query options

queryKey와 queryFn을 제외하고 몇가지 옵션을 더 받을 수 있다. 매우 많은 query option이 구현되어 있지만, 여기서는 내가 자주 사용하는 옵션에 대해서만 소개하겠다.

query options는 아래와 같은 형태로 작성한다.

  const { isLoading, data } = useQuery<CoinInterface[]>(
 	['allCoins'], 
    fetchCoins,
    { 	enabled: true,
    	refetchInterval: 3000
    }
  );

query option에 대한 추가적인 정보는 react query 공식 문서에서 확인할 수 있다.

react query documents

1. enabled (boolean)

  • enabled 는 쿼리가 자동으로 실행되지 않게 설정하는 옵션이다.

2. retry (boolean | number | (failureCount: number, error: TError) => boolean)

  • retry 는 실패한 쿼리를 재시도하는 옵션이다.
  • 기본적으로 쿼리 실패시 3번 재시도 한다.
  • true 로 설정하면 쿼리 실패시 무한 재시도하고 false로 설정하면 재시도를 하지 않는다.

3. staleTime (number | Infinity)

  • staleTime 은 데이터가 fresh 상태로 유지되는 시간이다. 해당 시간이 지나면 stale 상태가 된다.
  • default staleTime은 0 이다.
  • fresh 상태에서는 쿼리가 다시 mount 되어도 fetch가 실행되지 않는다.

4. cacheTime (number | Infinity)

  • cacheTime 은 inactive 상태인 캐시 데이터가 메모리에 남아있는 시간이다. 이 시간이 지나면 캐시 데이터는 garbage collector에 의해 메모리에서 제거된다.
  • default cacheTime 은 5분이다.

5. refetchOnMount (boolean | "always")

  • refetchOnMount 는 데이터가 stale 상태일 경우 마운트 시 마다 refetch를 실행하는 옵션이다.
  • default true
  • always 로 설정하면 마운트 시 마다 매번 refetch 를 실행한다.

6. refetchOnWindowFocus (boolean | "always")

  • refetchOnWindowFocus 는 데이터가 stale 상태일 경우 윈도우 포커싱 될 때 마다 refetch를 실행하는 옵션이다.
  • default true
  • always 로 설정하면 항상 윈도우 포커싱 될 때 마다 refetch를 실행한다는 의미이다.
  • QueryClient defaultOptions 설정으로 refetch 기능들을 다 false로 꺼버렸을 경우에는 refetch 기능이 실행되지 않는다. 그럴 경우엔 refetchOnWindowFocus 옵션이 실행되게끔 true로 설정하면 된다.
    fresh 상태인 1분 동안은 아무리 다른 탭을 왔다갔다해도 fetch 요청을 하지 않는다.

7. refetchOnReconnect (boolean | "always")

  • refetchOnReconnect 는 데이터가 stale 상태일 경우 재 연결 될 때 refetch를 실행하는 옵션이다.
  • default true
  • always 도 위에 두 옵션 처럼 쿼리가 매번 재 연결될 때 마다 refetch를 실행한다.

8. onSuccess ((data: TDdata) => void)

  • onSuccess는 쿼리 성공 시 실행되는 함수이다.
  • 매개변수 data는 성공 시 서버에서 넘어오는 response 값이다.

9. onError ((error: TError) => void)

  • onError 는 쿼리 실패 시 실행되는 함수이다.
  • 매개변수로 에러 값을 받을 수 있다.

10. onSettled ((data?: TData, error?: TError) => void)

  • onSettled 는 쿼리가 성공해서 성공한 데이터가 전달되거나, 실패해서 에러가 전달 될 때 실행되는 함수이다.
  • 매개변수로 성공 시엔 성공 데이터, 실패 시에는 에러가 전달된다.

11. initialData (TData | () => TData)

  • initialData 를 설정하면 쿼리 캐시의 초기 데이터로 사용된다. (쿼리가 아직 생성되지 않았거나 캐시되지 않았을 때)
  • staleTime 이 설정되지 않은 경우 초기 데이터는 기본적으로 stale 상태로 간주한다.

12. refetchInterval(number | false | ((data: TData | undefined, query: Query) => number | false)

  • 만약 number로 입력된다면 milliseconds 단위의 입력값마다 query가 refetch된다.
  • 만약 함수로 입력된다면, 해당 함수가 가장 최근 데이터와 쿼리에 대해 해당 빈도로 실행된다.
profile
저는 AI 개발자 '웅'입니다. AI 연구 및 개발 관련 잡다한 내용을 다룹니다 :)

0개의 댓글