[React] React Query 란?
1. React Query 소개
리액트 쿼리?
서버 상태를 관리하는 라이브러리
간단하게 알아보자
Client State vs. Server State ?
클라이언트 상태란 웹 브라우저 세션과 관련된 모든 정보를 의미한다.
예를들어 유저가 언어 선택이나, 다크모드 라이트모드 등 테마를 선택하는 경우
→ 서버에서 일어나는 일과는 관련이 없는 상태들, 단순히 사용자의 상태를 추적할때
서버 상태란 서버에 저장되며 클라이언트에 표시하는데 필요한 데이터
예를들어 데이터 베이스에서 불러오는 블로그 게시물 데이터
→ 여러 클라이언트에 표시할 수 있도록 서버에 저장되는 상태들
리액트 쿼리는 어떤 문제를 해결할 수 있을까?
리액트 쿼리는 클라이언트에 있는 서버 데이터 캐시를 관리한다.
리액트 코드에 서버 데이터가 필요할 때 Fetch / Axios 를 사용해 서버로 바로 이동하지 않고 리액트 쿼리 캐시를 서버에 요청하게 된다
여기서 리액트 쿼리는 클라이언트를 어떻게 구성했느냐에 따라 해당 캐시의 데이터를 유지 관리 할 수 있게 된다.
리액트 쿼리가 데이터를 관리하는 방법
리액트 쿼리는 키와 데이터 값을 갖는다.
두가지 방법으로 서버의 새로운 데이터와 캐시를 업데이트 할 수 있다.
- 명령: 기존의 데이터를 지우고 교체할 새 데이터를 서버에서 가져오게 함
- 선언: 예를들어 브라우저 창이 다시 포커스 될 경우 리페치 트리거를 설정한다
- staleTime 으로 언제 트리거 할 지 기간을 정할 수 있다.
리액트 쿼리의 장점
- 서버에 대한 모든 쿼리의 로딩 및 오류 상태를 자동으로 유지해준다.
- 페이징 처리, 무한 스크롤 등을 가능하게 하고, 필요한 경우 일부 데이터만 가져올 수도 있다.
- 사용자가 언제 해당 데이터를 사용할지 예측하여 프리페치가 가능하다.(미리 데이터를 캐시에 담아둘 수 있다.)
- 데이터 변이나 업데이트를 관리할 수 있다.
- 키를 이용하여 요청을 관리하기 때문에 중복 데이터들을 한번에 보낼 수 있다.( 중복 요청을 방지한다.)
- 에러 발생시 다시 요청할 수 있다.
- 성공/오류 콜백을 작성할 수 있다.
2. 사전 지식
리액트와 리액트 훅에 대한 지식 필요
Jest,Vitest(비테스트) 등에 대한 지식 필요
GraphQL과 비슷
클라이언트 사이드 렌더링 이용
https://tanstack.com/query/latest/docs/framework/react/overview
3. 가상 데이터로 블로그 만들기
https://jsonplaceholder.typicode.com/
jsonplaceholder: 가상의 블로그 서버의 엔드포인트를 제공(유저, 게시물, 댓글 등)
- 업데이트도 가능하지만, 응답만 받을 뿐, 데이터는 바뀌지 않는다.
<개발 기능>
- 페칭 데이타
- 로딩/에러 상태 관리
- 리액트 쿼리 개발자 도구
- 페이지네이션 작동방식
- 프리페칭(다음 게시글 미리 가져오기)
- 변이- Mutations(데이터 서버 변경)
- 라이브러리 설치
npm install @tanstack/react-query
라이브러리 버전 확인
npm list @tanstack/react-query
- query client 생성
- 쿼리 관리 및 서버 데이터도 저장하는 클라이언트
- QueryClientProvider을 추가하여 툴을 사용할 수 있다.
- QueryProvider 적용
- 자식 컴포넌트에 캐시 및 클라이언트 구성을 제공하는 공급자
- 쿼리 클라이언트를 값으로 가진다.
- useQuery 훅 호출
- 서버에서 데이터를 가지고 오는 훅
4. 쿼리 클라이언트 및 공급자 추가하기
// App.jsx
import { Posts } from "./Posts";
import "./App.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
function App() {
return (
// 쿼리 클라이언트 공급자 추가 - 캐시나 useQuery 같은 기능들을 사용 가능하다.
<QueryClientProvider client={queryClient}>
<div className="App">
<Posts />
</div>
</QueryClientProvider>
);
}
export default App;
쿼리 클라이언트를 생성하고,
최 상위 부모로 쿼리 클라이언트 공급자로 감싸 자식 컴포넌트에서 캐시나 useQuery 같은 기능들을 사용 가능하게 할 수 있다.
5. 컴포넌트에서 useQuery로 쿼리 생성하기
jsonplaceholder 에서 받아온 포스트 객체 리스트를 가지고
보여줄 예정(id, title)
import { useState } from "react";
import { useQuery } from "@tanstack/react-query"; // useQuery 추가
import { fetchPosts } from "./api";
export function Posts() {
// useQuery로 data를 구조분해 하여 데이터를 가져옴
const { data } = useQuery({
queryKey: ["posts"],
queryFn: fetchPosts,
});
if (!data) { // 동기 통신이기 때문에 값을 가져오는 데 시간이 소요됨, 미리 리턴 처리
return <div></div>;
}
return (
// ...
);
}
react-query의 useQuery 를 이용하여 원하는 기능을 실행할 수 있다.
예를 들어 블로그 리스트를 가지고 오는 fetchPosts를 실행한다고 했을 때, 미리 작성해 둔 async 함수를 불러와 사용할 수 있다.
export async function fetchPosts(pageNum = 1) {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts?_limit=10&_page=${pageNum}`
);
return response.json();
}
쿼리 키(queryKey) 와 함수(queryFn)를 정의해 useQuery의 객체 인수로 넣어 주면 아래와 같이 쿼리 객체를 받아오게 된다.
그 중 데이터 객체를 가져와야 하니 구조분해 하여 {data}만 담아 처리해 줄 수 있다.
그럼 아래와 같이 리스트를 잘 받아올 수 있다!
6. 로딩 상태와 에러 상태 처리하기
리엑트 useQuery의 반환 값 중에서는 데이터가 undefined 의 상태도 처리해 줄 수 있다.
https://tanstack.com/query/latest/docs/framework/react/reference/useQuery
isError
: 에러 발생 여부 boolean 값
isLoading
: 로딩중 상태 boolean 값
const { data, isError, isLoading } = useQuery({
queryKey: ["posts"],
queryFn: fetchPosts,
});
if (isLoading) {
return <div>🕺 로딩중 ...</div>;
}
로딩 화면을 보려면 크롬 개발자 도구 > 네트워크 > 네트워크제한을 ‘느린 3G’로 설정하면 천천히 진행 되어 로딩화면을 볼 수 있다.
isFetching vs. isLoading ?
isFetching은 비동기 쿼리가 아직 해결되지 않았다는 것을 의미. Axios / GraphQL 등의 호출을 통해 데이터를 가져오는 작업일 수 있음
isLoading은 쿼리 함수가 아직 해결되지 않았고, 캐시된 데이터도 없을 경우를 의미
→ 이전에 실행한적 없어서 데이터를 가져오는 중 + 캐시된 데이터도 없는 경우
! 페이지네이션 처리 시 캐시된 데이터가 있는지 없는지 구분하는 것이 중요
에러 처리도 동일하게 isError, error으로 확인 가능하다.
// throw new Error("호출 실패");
if (isError) {
return <div>`🚧 {error.toString()} 때문인것 같아요.. 뭔가 잘못 됐어요..`</div>;
}
리액트 쿼리는 기본적으로 3번 다시 시도 후 에러로 판별함. 시도중인 경우 로딩중으로 보여줌
7. React Query 개발자 도구
https://tanstack.com/query/latest/docs/framework/react/devtools
설치
$ npm i @tanstack/react-query-devtools
사용
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
QueryClientProvider 내부에 배치하면
아래와 같이 버튼을 화면에서 볼 수 있다.
기본적으로 개발환경에서만 버튼을 확인 가능하고 빌드 시 배포 환경에서는 해당 버튼을 확인할 수 없다.
비트프로젝트에서는 npm run dev 명령어로 서버를 개발 환경에서 테스트 했을 경우에는 잘 확인된다.
확인 가능한 기능>
- 모든 쿼리 상태( by key), 마지막 업데이트 시간
- 데이터 탐색기
- 쿼리 탐색기
등
8. staleTime vs gcTime
staleData? 사용 기간이 만료 되어 다시 가져올 준비가 된 데이터면서 여전히 캐시에 존재
→ 재검증이 필요
SWR ? Vercel에서 만든 ‘Stale, While Revalidating’의 약어, 서버에서 새 데이터를 가져와 최신 데이터를 보장함
데이터 리페치는 ‘staleData’일 경우에만 트리거 됨
예를들어, 컴포넌트가 리마운트 되거나, 윈도우가 리포커스 되는 경우
staleTime? 최대 수명으로 생각할 수 있음, 얼마나 오래 둘 건지 설정할 수 있다고 생각하면 된다.
const { data, isError, error, isLoading } = useQuery({
queryKey: ["posts"],
queryFn: fetchPosts,
staleTime: 2000, // 밀리초 단위(2초)
});
→ 2초동안 Fresh 상태를 유지한다.
기본적으로 0ms 이기 때문에 매번 서버에서 다시 가져와야 한다고 생각하면 된다. → 그럼 클라이언트에 stale 데이터가 있을 상황이 줄어들겠죠?
gcTime?
staleTime은 데이터를 다시 가져와야 할 때를 알려주고
gcTime은 데이터를 캐시에 유지할 시간을 결정
데이터가 페이지에 표시된 후부터 시간이 측정됨(기본 5분)
데이터와 연관된 활성 useQuery가 없고 해당 데이터가 현재 페이지에 표시되지 않으면 ‘cold storage’로 들어가게 된다
이후 gc 시간이 지나면 데이터는 캐시에서 사라진다.
페이지에 데이터가 표시될 때는 시간 측정을 하지않음!
시간이 지나면 garbage collected 되고 데이터는 사라진다.
- 캐시는 페칭 동안 보여줄 백업 데이타를 포함하고있다.
- ‘Fresh’상태고 캐시가 있다면 캐시 데이타를 보여주고, 리페치는 일어나지 않는다.(staleTime이 남아있고, gcTime은 진행되지 않음)
- ‘Stale’상태고 캐시가 있다면 캐시 데이터를 보여주고, 리페치가 일어난다.
- 캐시가 없다면, 리페치 하는 동안에는 아무것도 보여주지 않는다.