728x90

React Query

상태 관리 라이브러리란?


1. 서버 상태 관리 라이브러리

리액트에는 상태 관리 라이브러리의 큰 종류로 서버 상태, 클라이언트 상태가 존재한다.
클라이언트 상태 관리 라이브러리는 리덕스, 리코일, Zustand 등으로 존재한다.

2. 왜 상태 관리 라이브러리를 사용하나?

리덕스를 사용하는 이유:

<아래 글 참조>

https://koolreview.tistory.com/119

서버 상태 라이브러리를 사용하는 이유:

간단히 서버의 데이터를 가져와 관리해주는 라이브러리라 생각하면 된다.

Client vs Server State


Client state

앱 메모리에 유지되고 이를 액세스 하거나 업데이트하는 것은 동기식입니다.

Server state

원격으로 지속되며 가져오기 또는 업데이트를 위해 비동기식 API가 필요함

리액트 쿼리를 사용하면서 생각한 내용:


  • 클라이언트 상태 관리 라이브러리는 컴포넌트를 타고 타고 내려가는 props의 불편함 때문에 사용한다.
  • 서버 상태 관리 라이브러리는 useEffect와 useState 등을 사용하여 가져온 값을 새로운 값으로 유지하는 불편함을 해소하기 위해 사용한다.

용어 정리


 

fetching: 데이터를 가져오는 중

fresh: 데이터가 신선한(오래되지 않은) 상태

  • 서버와 현재 가지고 있는 데이터가 일치한다는 의미입니다.

stale: fresh의 반대 의미

  • 데이터가 오래되어 서버와의 데이터가 다를 것이다 가정한 상태입니다.

inactive: 사용하지 않는 상태

delete: 가비지 컬렉터에 의해 캐시에서 제거됩니다.

캐시란?

  • fetching 통해 받아온 값들을 메모리 상에 적재하는 곳입니다.

staleTime: fresh 상태에서 stale 상태로 변화하기까지의 시간

cascheTime: 캐시 메모리에 데이터가 유지되는 시간

Query: 질의문정도로 이해하면 됩니다.

QueryKey: 캐시 메모리에서 데이터를 꺼내올 때 사용하는 유일 키입니다.

흐름


상태는 총 5단계로, 그림과 같이 흘러갑니다.

특이한 점:

  • staleTime이 0이라면 바로 데이터가 썩은 상태(데이터가 일치하지 않다)라고 가정합니다.

설치


npm install react-query || yarn add react-query

React-Query 초기 설정


Bold 처리된 코드가 추가된 코드입니다.

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
**import { QueryClient, QueryClientProvider } from 'react-query';**
import App from './App';
import reportWebVitals from './reportWebVitals';

// Query Client를 생성
**const queryClient = new QueryClient()**
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    // Provider에 prop으로 넘겨준다.
  **<QueryClientProvider client={queryClient}>**
    <React.StrictMode>
      <App />
    </React.StrictMode>
  **</QueryClientProvider>**
);

reportWebVitals();

최상단 파일에서 HTML 태그를 QueryClientPrivoider 태그로 감싸주고, client props에 QueryClient를 넘겨줍니다.

데이터 가져오는 방법


UseQuery

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

const fetchDataFunction = () => {
    return axios.get('주소')
}

export const example = () => {

        // 방법 1
    const {isLoading, data} = useQuery('text-unique-key', () => {
        // function
        return axios.get('주소')
    })

      // 방법 2
    const {isLoading, data} = useQuery('text-unique-key', fetchDataFunction);

    if(isLoading){
        return <h2>Loading</h2>
    }

    return <>

    </>
}

useQuery를 통해 값을 읽어옵니다. 첫 번째 파라미터인 text-unique-key는 쿼리를 구분하는 키로 사용됩니다.

  • 방법 1과 2의 차이는 함수를 내부에 적거나, 외부로 빼서 사용하는 차이가 존재합니다.

에러 및 로딩 다루기

const {isLoading, isError, error} = useQuery('text-unique-key', fetchDataFunction);

if(isLoading){
    return <h2>Loading</h2>
}
if(isError){
    return <h2>{error}</h2>
}

Query에서 isLoading과 isError를 넘겨주어 각 상태에 따라 반환하는 값을 달리하여 화면을 상태별로 다룰 수 있다.

DevTools 띄우기

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import { QueryClient, QueryClientProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import App from './App';
import reportWebVitals from './reportWebVitals';

const queryClient = new QueryClient()
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <QueryClientProvider client={queryClient}>
    <React.StrictMode>

    </React.StrictMode>
    **<ReactQueryDevtools initialIsOpen={false} position='bottom-right' />**
  </QueryClientProvider>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
`<ReactQueryDevtools initialIsOpen={false} position='bottom-right' />`

이 코드를 작성하여 초기 오픈을 할지 말지, 어느 위치에 둘 것인지 선택 가능하다

오른쪽 하단에 꽃 모양으로 뜨며, 클릭 시 아래처럼 펼쳐진다.

마우스가 가리키는 곳이 유니크 키이며 클릭 시 하단과 같이 패널이 뜬다.

이 패널에서 액션을 실행할 수도 있고, 데이터에 대한 값에 대해 볼 수도 있다.

Query Cache


같은 데이터를 매번 받아 사용하면 데이터를 받아오기 때문에 로딩이 발생한다. 그렇기에 캐싱 기능을 사용하면 한번 받아온 데이터를 저장하여 페이지를 로딩 없이 사용할 수 있다.

기본적으로 useQuery 이용 시 staleTime은 0, cacheTime 5분으로 설정되어 캐싱은 자동으로 작동한다.

Stale Time

데이터가 자주 변경되지 않는 데이터들은 네트워크 요청을 하여 데이터를 수시로 가져올 필요가 없다. 그렇기에 데이터 요청 수를 줄이기 위해 staleTime을 조절한다.

특이한 개념 윈도 포커스 ( 탭 이동? )

브라우저 탭을 이동하다 돌아오면 오래되어버린 데이터, steal 상태의 데이터를 다시 가져온다. 하지만 staleTime 시간 동안에는 다시 받아오지 않는다.

다시 정리하자면, 기본적으로 캐싱은 5분으로 진행된다. 이는 곧 메모리에 저장된다는 의미이고, Query를 실행하는 페이지에 이동되어도 이미 데이터가 있기 때문에 로딩이 발생하지 않는다.

하지만 stealTime은 기본 값이 0 임으로 데이터를 받아온 순간부터 받아온 데이터는 썩어버린 데이터 취급을 받게 되고, 이에 따라 윈도우 포커스가 변하여 다시 원래 페이지를 가리키면 데이터 다시 받아온다.

캐시 메모리에 저장된 데이터가 cachTime이 지난 이후에 delete가 된다는 사실도 기억하자. 그렇기에 데이터가 삭제된 이후에는 loading이 발생한다.

const { isLoading, data, isError, error, isFetching } = useQuery(
    'text-unique-key',
     fetchDataFunction**,{
       cacheTime: 5000,
       staleTime: 0
     }**);
실제 time 명시는 위와 같이 한다. 하지만 기본 값과 동일함으로 적지 않아도 된다.

useQuery 옵션들

enabled: boolean

쿼리를 자동으로 실행되지 않도록 해주는 옵션

const { data } = useQuery(['todo', id], () => fetch(),{
    enabled: false
})

refetchOnMount: boolean | “always”

데이터가 stale 상태 시 마운트 할 때마다 refetch를 실행하는 옵션
default → true이며, always로 설정 시 마운트 시마다 매번 refetch를 실행한다.

const { data } = useQuery(['todo', id], () => fetch(),{
    refetchOnMount: true | false | "always"
})

refetchOnWindowFocus ⇒ boolean | “always”

데이터가 stale상태일 경우 윈도우 포커싱 될 때마 refetch가 실행된다.
default true, always는 항상 윈도우 포커싱마다 실행된다는 의미이다.

주기적으로 다시 받아오기

const { data } = useQuery(['todo', id], () => fetch(),{
    refetchInterval: 2000, // 2초마다 다시 받아옵니다.
    refetchIntervalInBackground: true, // 포커스를 잃은 경우, 백그라운드에서도 계속 동작하도록
})

OnClick마다 받아오도록 코드 작성

const { data, refetch } = useQuery(['todo', id], () => fetch(),{
    enabled: false,
})

enabled로 false로 만든 후 button의 onClick에 refetch functnion을 넣어서 클릭 시마다 호출하도록 할 수 있다.

Side Effect 처리

  • 일단 화면에 데이터를 보여 주는 용도로 사용
  • 사용자가 경험 측면에서 의미가 있음
export const RQSuperHeroesPage = () => {
    const onSuccess = () => {
        console.log('Perform side effect after data fetching')
    }

    const onError = () => {
        console.log('Perform side effect after encountering error')
    }

    const { isLodading, data isError, error, isFetching, refetch } = useQuery(
                    'super-heroes',fetchSuperHerores,{
                    onSuccess,
                    onError,
                })

onSuccess, onError 옵션에 따라 직접 만든 함수를 실행할 수 있다.

  • 위 onSuccess와 onError가 동일한 이름이기 때문에 추가적으로 작성하는 코드가 없다.(es6문법)

필터링

const { isLoading, data, isError, error, isFetching, refetch } = useQuery(
    'super-heroes', fetchSuperHerores, {
        onSuccess,
        onError,
        select: (data) => {
            const superHeroNames = data.data.map((hero)=> hero.name)
            return superHeroNames
        },
    }
)

select로 데이터를 선택(필터링)해서 사용 가능하다

  • 직접 사용하는 모습

커스텀 훅

따로 파일로 빼두어 사용하는 것을 커스텀 훅이라 한다.

동적 쿼리

id값을 넘겨서 주소 넣어 값을 받아온다.

멀티 쿼리

data의 이름이 겹쳐, :를 이용하여 이름을 바꿔 사용한다.

동적 병렬 쿼리

useQueries를 통해 배열에 map을 이용하여 쿼리 키와 쿼리 함수를 넘겨 사용한다.

쿼리 의존 상태

channelId가 의존 상태(user를 호출받은 다음 id값이 생겨서 의존 관계)이므로 있는 경우에 가져오려면, enabled 옵션을 사용한다.
enabled로 처음에는 false였다 true로 바뀌면서 값을 가져온다.

목록 → 아이템 쿼리 수 줄이기

상품 목록을 가져오고, 상품 상세 페이지 시 두 번의 쿼리 요청이 발생함.

같은 값인데 동일하게 가져오니 비효율적이며, 네트워크가 느린 곳에서는 좋은 성능을 발휘하지 못한다.

그러므로 useQueryClient를 이용하여 QueryClient에서 유일 키를 getQueryData에 넣어 값을 호출하여 사용한다.

Pagenation Query

keepPreviousData 옵션을 이용하여 새로운 데이터 요청 동안 마지막 data 값을 유지한다. 그렇기에 목록이 사라지는 깜빡임 현상을 방지할 수 있다.

무한 쿼리 페이지

외국 사이트의 쇼핑몰 사이트의 more버튼을 계속 누를 때 사용되는 query로 무한으로 요청함.

mutation (업데이트)

성공 시 invalidateQueryies로 값을 버려버린다.(무효화시킨다)

업데이트 시 가져온 데이터 관리

업데이트 쿼리 최적화

각 상태별로 로직을 따로 처리한다.

Axios 편리하게 사용하기

반응형

+ Recent posts