리액트 쿼리란?
<공식문서> 리액트 쿼리는 React 애플리케이션에서 서버 상태를 가져오고, 캐싱하고, 동기화하고 업데이트하는 작업을 쉽게 만듭니다.
리액트 쿼리는 서버 데이터 캐시를 관리한다. 리액트 코드에 서버 데이터가 필요할 때
Fetch
나 Axios
를 사용해 서버로 바로 이동하지 않고 리액트 쿼리를 캐시를 요청한다. 리액트 쿼리의 역할은 리액트 쿼리 클라이언트를 어떻게 구성했느냐 해당 캐시의 데이터를 유지 관리하는 것이다. 데이터를 관리하는 것은 리액트 쿼리지만 서버의 새 데이터로 캐시를 업데이트하는 시기를 설정하는 것은 사용자의 몫이다. 쉽게 말해 리액트 쿼리는 비동기적으로 데이터를 간편히 다룰 수 있게 해준다.도구
- React Query Devtools를 통해 개발자도구를 브라우저에서 간편하게 확인할 수 있다.
- React-Query에 탑재되어 있어 별도 설치 없이 기능을 쓸 수 있다.
NODE_ENV
리액트 쿼리 개발자툴은 노드 환경을 변경할 수 있을때만 보이게 된다. 노드 환경이 프로덕션으로 설정되어 있지않기 때문에 이걸 cra로 개발 모드에 있을 때 노드 환경은 개발 할 수 있다. 직접 만들어서 빌드를 실행하는 경우 노드 환경은 프로덕션이 된다. 개발 모드에서 실행하는 동안은 볼 수 있지만 직접 만든 경우는 도구가 보이지 않는다. cra로 환경셋팅 한 경우 NODE_ENV 환경 변수가 내장으로 자동으로 셋팅 되어지기 때문에 노출 위험이 줄어든다.
상태
- 쿼리 데이터는 5가지 상태를 가진다.
fresh : 새롭게 추가된 쿼리 인스턴스이며, 만료되지 않은 쿼리이다. 컴포넌트의 mount, update 시에 데이터를 재요청하지 않는다.
stale : 데이터 패칭이 완료되어 만료된 쿼리이다. stale 상태의 같은 쿼리를 useQuery로 재호출하여 컴포넌트 마운트를 한다면 캐싱된 데이터가 반환된다.
fetching : 요청 상태인 쿼리이다.
inactive : 비활성 쿼리로써 사용하지 않는다. 5분 뒤에 가비지 콜렉터가 캐시에서 제거한다.
delete : 가비지 콜렉터에 의하여 캐시에서 제거된 쿼리이다.
1. 쿼리 생성
-> npm install react-query
패키지 다운로드 후
QueryClient
를 생성해서 QueryProvider
에 추가 했다. 왜냐하면 프로바이더로 감싸야 모든 자식 컴포넌트가 캐시와 훅을 사용할 수 있기 때문이다.// 전역으로 사용할 queryClient 생성 const queryClient = new QueryClient(); function App() { return ( <QueryClientProvider client={queryClient}> ... <ReactQueryDevtools /> </QueryClientProvider> ); }
2. 데이터 캐싱 & 페칭
- 리액트 쿼리는 가장 기본적인 데이터 패칭 기능과 로딩 및 오류 상태 관리이다.
useQuery
훅을 이용하여 데이터를 서버에서 가져오고 최신 상태인지 확인 할 수 있다.
- 기본적인
useQuery
구문이다.
const { ... } = useQuery([""], 비동기 함수, 옵션)
isFecthing, isLoading
useQuery에서 많이 사용하는 프로퍼티는 isLoading, isFetching, error이다. isFecthing은 비동기 쿼리가 해결되지 않았을 때 아직 페칭을 완료하지 않았다는 의미지만 쿼리가 비동기로 API를 호출을 할 수 있다. 또한 캐싱된 데이터 여부와 상관없이 항상 나타낸다. isLoading은 가져오는 상태에 있음을 의미, 쿼리 함수가 아직 해결되지 않은 것이다. 그리고 캐시된 데이터도 없다. 데이터를 가져오는 중이고 표시할 캐시 데이터도 없다는 것이다. isLoading은 캐시된 데이터가 없고 데이터를 가져오는 상황에 해당한다. isLoading이 참이면 isFetching도 참이다. 이 둘의 차이점은 페이지네이션을 할 때 캐시된 데이터가 있을 때와 없을때를 구분해야 한다는 사실을 알게 될 것이다. 이를 통해 데이터가 로딩되었을 때와 isLoading, isError 이 둘은 데이터가 로딩 중인지 여부와 데이터를 가져올 때 오류가 있는지 여부를 알려주는 불리언이다.
const { data, isError, error, isLoading } = useQuery( ... ); if (isLoading) return <h2>Loading....</h2>; if (isError) return ( <> <h2>잘못된 정보입니다.</h2> <p>{error.toString()}</p> </> );
useQuery
를 심화적으로 사용하면 프로퍼티 구조 분해, 키 값, 비동기 함수(파라미터), 옵션(에러 캐치)을 기준으로 아래와 같이 사용 가능 하다. 객체의 프로퍼티를 구조분해 해서 객체로 반환할 수 있다.const { data: { 구조 분해할 변수 } = {} } = useQuery( [키 값, 파라미터 넘길 값], () => 비동기 함수({ 파라미터 받을 값 }), { 에러 처리 } );
3. 에러 핸들러
기존에는 아래의 방식으로 에러를 캐치했다.
onError(error) { const title = error instanceof Error ? error.message : '서버 연결 오류입니다.'; },
하지만 이렇게
queryClient
의 옵션값에 커스텀훅을 전역으로 설정해주어 에러를 외부에 위임함으로써 에러의 내용을 외부에서 알려주기 때문에 유지보수와 안전하고 정확한 에러 캐치가 가능하다.// 에러 핸들러 커스텀훅 function queryErrorHandler(error: unknown): void { const title = error instanceof Error ? error.message : '서버 연결 오류입니다.'; // queryClient 초기설정 export const queryClient = new QueryClient({ defaultOptions: { queries: { onError: queryErrorHandler, }, }, }) }
추가적으로 아래와 같이
queryClient
의 옵션값에 전역 설정을 넣는 경우는 그만큼 정보의 변동이 적거나 사용자 세션 동안 정보 변동이 거의 없는 앱이나 페이지에는 괜찮은 방식이다.staleTime: 60000, // 10분 cacheTime: 90000, // 15분 refetchOnWindowFocus: false, refetchOnMount: false, refetchOnReconnect: false
댓글