useQuery 사용법

송은·2023년 6월 13일
0
post-thumbnail

React Query Logo

useQuery

useQuery는 React Query를 이용해 서버로부터 데이터를 조회해올 때 사용한다.

데이터 조회가 아닌 데이터 변경 작업을 할 때는 useMutation을 사용한다.

useQuery는 다음과 같은 형태로 사용된다.

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

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

시작하기

React Query를 설치해주고, app을 다음과 같이 변경해준다.

yarn add react-query
// app.tsx
import { QueryClient, QueryClientProvider } from "react-query";
import Main from "./components/Main";

const queryClient = new QueryClient();

function App() {
  return (
    <div className="App">
      <QueryClientProvider client={queryClient}>
        <Main />
      </QueryClientProvider>
    </div>
  );
}

export default App;

QueryClient를 이용하여 queryClient 인스턴스를 생성해준 뒤, 이것을 QueryClientProvider의 props로 전달해준다.


queryKey

queryKey는 useQuery마다 부여되는 고유 Key 값이다.

해당 Key 값은 단순하게 문자열로 사용될 수도 있고 또한 배열의 형태로도 사용될 수 있다.

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

// 배열
const res = useQuery(['persons'], queryFn)
const res = useQuery(['persons', 'addId'], queryFn)
const res = useQuery(['addId','persons'], queryFn)
const res = useQuery(['persons', {type: 'add', name: 'Id'}], queryFn)

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

문자열로 작성된 경우에는 자동으로 길이가 1인 배열로 인식하기 때문에 결과적으로 ["persons"] 라고 입력한 것과 동일한 queryKey로 작성된다.

또한 ["persons", "addId"]["addId", "persons"]는 queryKey가 동일해보이지만 React Query에게는 동일하지 않은 queryKey로 인식된다.

이는 queryKey가 할당될 때 배열에 입력되는 순서도 보장해주기 때문이다.

👉 이렇게 queryKey를 통해 고유한 키 값을 부여함으로써 React Query가 query 캐싱을 관리할 수 있도록 해준다.

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

const Query = (): JSX.Element => {

    const getPersons1 = () => {
        const res1 = useQuery(['persons'], queryFn1);
    }
    
    const getPersons2 = () => {
        const res2 = useQuery(['persons'], queryFn2);
    }

    return (
        <div>
            {getPersons1()}
            {getPersons2()}
        </div>
    )
}

export default Query;

위의 코드에서 res1res2가 동일한 queryKey를 사용하며 서버에 있는 데이터를 조회해오려고 하고 있다.

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

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

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

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


queryFn

queryFn은 query Function으로 promise 처리가 이루어지는 함수라고 생각하면 된다.

다른 말로는 axios를 이용해 서버에 API 요청하는 코드라고 생각할 수 있다.

그렇기 때문에 useQuery는 결과적으로 다음과 같은 형태로 코드가 작성된다.

// 1
const res = useQuery(['persons'], () => axios.get('http://localhost:8080/persons'));

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

staleTime / cacheTime

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

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

const Query = (): JSX.Element => {

    const getPersons = () => {
        const res = useQuery(['persons'], () => axios.get('http://localhost:8080/persons')); // API 호출

        // 로딩 중일 경우
        if(res.isLoading) {
            return (
                <LoadingText>Loading...</LoadingText>
            )
        }

        // 결과값이 전달되었을 경우
        if(res.data) {
            const persons: 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>
            {getPersons()}
        </Wrapper>
    )
}

export default Query;

서버를 실행시킨 뒤 작성한 파일을 화면에 띄우면 다음과 같다.

useQuery 요청 화면

여기서 Network탭을 확인해보면 단순히 탭 페이지 전환만 있음에도 불구하고 persons를 지속적으로 호출한다.

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


staleTime

refetch가 발생되는 이유는 queryKey에 매핑되는 데이터가 "fresh" 하지 않고 "stale" 해졌기 때문이다.
stale은 탁한, 신선하지 않은 이라는 의미를 가지는데 데이터가 stale해졌다는 것은 결국 오래된 데이터라고 생각할 수 있다.

stale한 데이터를 사용자에게 보여주는 것은 유의미하지 않다고 React Query는 판단하고 fresh한 데이터를 요구하게 된다.
그리고 결국 서버로부터 fresh한 데이터를 전달받기 위해 refetch가 이루어진다.

개발자의 입장에서는 데이터가 stale 하지 않다고 생각할 수 있지만 React Query는 계속해서 refetch를 수행한다.

왜냐하면 staleTime의 default값이 0초이기 때문이다.

한 번 데이터를 조회해오면 그 순간 바로 해당 데이터는 stale한 데이터이기 때문에 refetch가 계속해서 발생되는 것이다.


cacheTime

이런 staleTime과 유사한 역할을 수행하는 것에는 cacheTime이 있다.

cacheTime캐싱 처리가 이루어지는 시간을 의미한다. default값은 5분으로 설정되어 있다.

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

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

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

그렇기 때문에 두 설정을 모두 고려하여 코드를 구현해야 된다.


설정방법

staleTimecacheTime의 설정 방법은 useQuery 작성 시 다음과 같이 설정해줄 수 있다.

작성되는 시간은 ms 단위이기 때문에 1000 = 1초 라고 생각하면 된다.

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

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

useQuery Options

refetch window focus 설정

staleTime에 대한 개념을 알게 됐더라도 조금 낯선 부분은 단순 페이지 전환만으로 refetch가 이루어진다는 것이다.

이런 동작이 수행되는 이유는 default로 window focus 설정이 true로 되어 있기 때문이다.

어떤 상황에서는 이런 기능이 더 효율적으로 사용될 수는 있지만 또 다른 상황에서는 필요없는 기능일 수도 있다.

그럴 때는 window focus 설정을 false로 변경하여 staleTime이 지났더라도 focus가 다시 되는 것만으로 refetch가 발생되지 않게 설정해줄 수 있다.

참고로 window focus 설정이 false로 변경된 후 staleTime이 지났을 때 refetch 되는 경우로는 다른 화면으로 이동되었다가 다시 현재 화면으로 되돌아오는 케이스가 있다.

설정하는 방법은 2가지가 있다.

1. 전역 설정

QueryClient를 생성하기 위해 설정했던 코드에 아래와 같이 refetchOnWindowFocus 옵션을 false로 설정해주면 전역적으로 적용시킬 수 있다.

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')
);

2. 각각 설정

두번째로는 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에서는 if문을 사용하지 않고 useQuery에서 제공해주는 query 자동 실행 설정을 통해 동일한 결과를 만들어 줄 수 있다.

// options에 enabled 설정을 추가해줄 수 있다.
useQuery(queryKey, queryFn, options)

// 1
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
개발자

0개의 댓글