React Query - useQuery()

gyu0714·2022년 11월 7일
0

Hooks

목록 보기
8/9
post-thumbnail

useQuery

useQuery는 React Query를 이용해서 서버로부터 데이터를 조회해올 때 사용한다.
💡 데이터 조회가 아닌 데이터 변경 작업은 useMutation을 사용한다.

DB로 비유를 하자면 SELECT를 할 때 사용된다고도 할수있다.

useQeury를 코드로 작성하여 구현하기 위해서는 2가지 개념이 있다.

  • queryKey
  • queryFunction
const res = useQuery(queryKey, queryFn);

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

queryKey?

queryKey는 useQuery마다 부여되는 고유 Key 값이다.
해당 Key 값은 단순하게 문자열로 사용될 수도 있고 또한 배열의 형태로도 사용될 수 있다.
그렇기 때문에 실제로 사용될 때 다음과 같은 방식으로 코드가 작성된다.

// 문자열
const res = useQuery('persons', queryFunction);

// 배열 - 1
const res = useQuery(['persons'], queryFunction);

// 배열 - 2
const res = useQuery(['persons', 'add Id'], queryFunction);

// 배열 - 3
const res.= useQuery(['add Id', 'persons'], queryFunction);

// 배열 - 4
const res = useQuery(['persons', {type: 'add', name: 'Id'}], queryFunction);

"문자열"은 현재 "persons"라는 문자열이 queryKey로 사용되고 있다.

React Query는 이렇게 문자열로 작성된 경우에는 자동으로 길이가 1인 배열로 인식하기 때문에 결과적으로 "배열 1"과 동일한 queryKey로 작성되었다는 것을 알 수 있다.

또한 "배열 2" 와 "배열 3"은 queryKey가 동일해 보이지만 React Query에게는 동일하지 않은 queryKey로 인식된다.

왜냐하면 queryKey가 할당될 때 배열에 입력된느 순서도 보장해주기 때문이다.

queryKey 역할

queryKey의 역할은 React Query가 query 캐싱을 관리할 수 있도록 도와준다.

import React from "react";
import { useQuery } from 'react-query';

const Query = (): JSX.Element => {
	const getPersons1 = () => {
    	const res1 = useQuery(['persons'], queryFunction1);
    }
    
    const getPersons2 = () => {
    	const res2 = useQuery(['persons'], queryFunction2);
    }
    
    return (
    	<div>
        	{getPerson1()}
            {getPersons2()}
        </div>
    )
}

export default Query;

res1과 res2가 동일한 queryKey를 사용하며 서버에 있는 데이터를 조회해오려고 하고 있다.

일반적인 상황에서는 res1과 res2에 대한 모든 요청이 이루어지게 되므로 서버에 2개의 request가 전달 된다.

하지만 해당 코드에서는 서버에 1개의 request만 전달이 된다.

왜냐하면 res1에서 request를 서버에 전달하게 되면 res2에서는 이미 동일한 queryKey에 대한 결과값이 있기 때문에 추가 요청을 하지 않고 res1의 결과를 그대로 가져와 사용하기 때문이다.

또한 queryFunction이 다르게 정의 되어 있더라도 res2에서는 res1의 결과를 그대로 전달받기 때문에 queryFunction1이 처리된 결과를 확인할 수 있다.

queryFunction?

queryFunction은 Promise 처리가 이루어지는 함수라고 생각하면 된다.
다른 말로는 fetch함수나 axios를 이용해 서버에 API요청하는 코드라고 생각 할 수 있다.
그렇기 때문에 useQeury는 결과적으로 다음과 같은 형태로 코드가 작성된다.

// 1st
const res = useQuery(['persons'], () => fetch(('http://localhos:8080/person', {
method: 'GET'
}).then((res) => res.json()))

// 2nd
const res = useQuery({
	queryKey: ['person'],
    queryFunction: () => fetch('http://localhost:8080/person').then(res => res.json())
})

staleTime 과 cacheTime

import * as React from 'react';
import {useQuery} from 'react-query';

interface Iperson {
  id: number;
  name: string;
  phone: string;
  age: number;
}

const Query = (): JSX.Element => {
  const getPerson = () => {
    const res = useQuery(['person'], () =>
      fetch('http://localhost:8080/person').then((res) => res.json()),
    );
    // Loading
    if (res.isLoading) {
      return <LoadingText>Loading...</LoadingText>;
    }
    // 결과값이 전달 되었을 경우
    if (res.data) {
      const person: Iperson[] = res.data.data;

      return (
        <Person.Container>
          {persons.map((person) => {
            return (
              <Person.Box key={person.id}>
                <Person.Title>{person.id}.</Person.Title>
                <Person.Text>{person.name}</Person.Text>
                <Person.Text>({person.age})</Person.Text>
              </Person.Box>
            );
          })}
        </Person.Container>
      );
    }
  };

  return <Wrapper>{getPerson()}</Wrapper>;
};

export default Query;

여기서 확인해야 되는 부분은 Network의 person을 호출하는 것이다.
Network를 확인 해보면 단순히 페이지 전환만 했음에도 불구하고 person을 지속적으로 호출하고 있다.

이처럼 호출이 지속적으로 발생되는 이유는 자동으로 refetch가 이루어지고 있기 때문이다.

그리고 refresh가 발생되는 이뉴는 해당 queryKey에 매핑되는 데이터가 fresh하지 않고 stale 해졌기 때문이다.

stale은 탁한, 신선하지 않은 이라는 의미를 가지는데 데이터가 stale 해졌다는 것은 결국 오래된 데이터라고 생각 할 수있다.

stale 한 데이터를 사용자에게 보여주는 것은 유의미하지 않다고 React Query는 판단하고 fresh한 데이터를 요구하게 된다.

그래서 결국 서버로부터 fresh한 데이터를 전달받기 위해 refresh가 이루어진다.

하지만 여기서 개발자는 데이터가 stale하다고 생각하지 않을수도 있다.
그럼에도 불구하고 React Query는 계속해서 refetch를 수행한다.
왜냐하면 default 값으로 staleTime은 0초이기 때문이다.
한 번 데이터를 조회해오면 그 순간 바로 해당 데이터는 stale한 데이터이기 때문에 refetch가 계속해서 발생되고 있는 것이다.

이런 staleTime과 유사한 역할을 수행하는 것에는 cacheTime이 있다. cacheTime은 말 그대로 캐싱 처리가 이루어지는 시간을 의미한다. cacheTime은 default 값으로 5분으로 설정되어 있다.

그렇기 때문에 queryKey에 매핑되는 데이터가 사용되지 않는 시점을 기준으로 5분이 지나지 않은 상태에서 해당 queryKey를 다시 호출할 경우 이전에 가져왔던 데이터를 다시 보여주게 된다.

하지만 5분이 지나게 되면 캐시 GC 타이머가 실행되며 기존 데이터는 삭제 처리가 이루어지고 queryKey를 다시 호출하게 되면 서버에 다시 데이터를 요청하게 된다.

즉, useQuery에는 staleTime, cacheTime 두 개념이 모두 존재하기 때문에 둘 중 하나라도 만족되지 않으면 서버에 다시 데이터를 요청하게 된다.

그렇기 때문에 두 설정을 모두 고려하며 코드를 구현해야 될 것이라고 여겨진다.

지금까지 말한 staleTime, cacheTime을 설정하는 방법은 useQuery를 작성할 때 함께 해줄 수 있다.

// 1st
const res = useQuery(['person'], () => axios.get('http://localhost:8080/person'), {
	staleTime: 5000 // 5초
    cacheTime: Infinity, // 제한없음
});

// 2nd
const res = useQuery({
	queryKey: ['person'],
    queryFunction: () => axios.get('http://localhost:8080/person'),
    staleTime: 5000, // 5초
    cacheTime: Infinity // 제한없음
});

refetch window focus 설정

staleTime에 대한 개념을 알게 되었더라도 조금 낯선 부분은 단순 페이지 전환만으로 refetch가 이루어진다는 것이다. 이런 동작이 수행되는 이유는 default로 window focus 설정이 true로 되어 있기 때문이다.

어떤 상황에서는 이런 기능이 더 효율적으로 사용될 수는 있지만 또 다른 상황에서는 필요 없는 기능일 수도 있다. 그럴때는 window focus 설정을 false로 변경하여 staleTime이 지났더라도 focus가 다시 되는 것만으로 refetch가 발생되지 않게 설정해줄 수 있다.

참조적으로 window focus 설정이 false로 변경된 후 staleTime이 지났을 때 refetch 되는 경우로는 다른 화면으로 이동되었다가 다시 현재 화면으로 되돌아오는 케이스가 있다.
첫 번째는 전역으로 설정하는 방법이다.

import * as React from 'react';
import ReactDom from 'react-dom';
import { QueryClient, QueryClientProvider } from 'react-query';
import App from './App';

const queryClient = new QueryClient(
    {
        defaultOptions: {
            queries: {
                refetchOnWindowFocus: false, // window focus 설정
            }
        }
    }
); // queryClient 생성

ReactDom.render(
    // App에 QueryClient 제공
    <QueryClientProvider client={queryClient}>
        <App />
    </QueryClientProvider>, 
    document.querySelector('#root')
);

여기서 QueryClient를 생성할 때 다음과 같이 window focus 설정을 false로 해주면 전역적으로 적용이 될 수 있다.

두 번째는 useQuery마다 설정하는 방법이다.
useQuery를 생성할 때 다음과 같이 각각 window focus 설정을 변경해줄 수 있다.

// 1
const res = useQuery(['persons'], () => axios.get('http://localhost:8080/persons'), {
    refetchOnWindowFocus: false // window focus 설정
});

// 2
const res = useQuery({
    queryKey: ['persons'],
    queryFn: () => axios.get('http://localhost:8080/persons'),
    refetchOnWindowFocus: false // window focus 설정
});

query 자동 실행 설정

코드를 구현하다 보면 다음과 같이 조건이 맞을ㄷ 때만 서버에 데이터 요청을 하고 조건이 맞지 않으면 코드 실행을 막는 경우가 있다.

if (id) {
	const res = axios.get('http://localhost:8080/person', {
    	params: {
        	id: id,
        }
    })
}

useQuery를 사용할 때도 당연히 동일한 상황이 생길 수가 있다.

하지만 useQuery에서는 if문을 사용하지 않고 useQuery에서 제공해주는 query 자동 실행 설정을 통해 동일한 결과를 만들어 줄 수 있다.

위와 동일한 결과가 나오는 코드를 다음과 같이 작성해줄 수 있다.

const res = useQuery(['person', id], () => axios.get('http://localhost:8080/person', {
	params: {
    	id: id,
    }
}), {
	enabled: !!id
});

// 2
const res1 = useQuery({
    queryKey: ['person', id],
    queryFn: () => axios.get('http://localhost:8080/person', {
        params: {
            id: id,
        }
    }),
    enabled: !!id // 코드 자동 실행 설정
});

enabled의 default 값은 true로 되어 있다.

하지만 위와 같이 id값이 존재하지 않을 경우 false를 변경해줌으로서 자동 실행을 막을수 있게 도와준다.

profile
gyu0714

0개의 댓글