React-Query란?
서버로부터 데이터 fetching, caching, 서버 데이터와의 동기화 및 업데이트 등 데이터를 효율적으로 관리할 수 있도록 하는 라이브러리
** React-query v4부터는 패키지 이름이 @tanstack/react-query로 변경되면서 React에만 국한되지 않은 범용 프론트엔드 라이브러리로 개선되었다.
왜 필요하나?
대표적인 기능은 다음과 같다.
- 데이터 가져오기 및 캐싱
- 동일 요청의 중복 방지
- 자동 새로고침을 통해 데이터 최신 상태 유지
- 성능 최적화: 무한스크롤, 페이지네이션 등
사용 방법
아래는 공식문서에 있는 예시 코드이다.
세가지 핵심 개념인 Queries, Mutations, Query Invalidation 정도만 알면
React-Query를 바로 시작해볼 수 있다.
해당 개념을 위주로 코드를 훑어보며 감을 잡아보자.
import {
useQuery,
useMutation,
useQueryClient,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'
import { getTodos, postTodo } from '../my-api'
// Create a client
const queryClient = new QueryClient()
function App() {
return (
// Provide the client to your App
<QueryClientProvider client={queryClient}>
<Todos />
</QueryClientProvider>
)
}
function Todos() {
// Access the client
const queryClient = useQueryClient()
// Queries
const query = useQuery({ queryKey: ['todos'], queryFn: getTodos })
// Mutations
const mutation = useMutation({
mutationFn: postTodo,
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
})
return (
<div>
<ul>{query.data?.map((todo) => <li key={todo.id}>{todo.title}</li>)}</ul>
<button
onClick={() => {
mutation.mutate({
id: Date.now(),
title: 'Do Laundry',
})
}}
>
Add Todo
</button>
</div>
)
}
render(<App />, document.getElementById('root'))
Queries
쿼리란 서버에서 데이터를 가져오는 방법을 정리한 약속이라고 할 수 있다.
각각의 쿼리는 고유한 키를 가지고 있고, Promise 기반의 메소드로 데이터를 받아온다.
import { useQuery } from '@tanstack/react-query'
function App() {
const info = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList })
}
fetchTodoList를 통해 데이터를 서버에서 받아오고,
해당 데이터에 todos라는 key를 붙여, 데이터 재사용 및 캐싱, refetching에 용이해진다.
- 첫 번째 파라미터에 들어가는 배열의 첫 요소는 unique key로 사용되고,
두 번째 요소부터는 query 함수 내부의 파라미터로 값들이 전달된다. - 두 번째 파라미터로 실제 호출하고자 하는 비동기 함수가 들어간다. 이때 함수는 Promise를 반환하는 형태여야 한다
- 최종 반환 값은 API의 성공, 실패 여부, 반환값을 포함한 객체이다
쿼리는 한번에 하나의 상태만 가질 수 있는데, 다음 중 하나이다.
- isPending (status == 'pending)
- 아직 데이터를 가져오지 못한 상태
- 데이터 요청 중 로딩 화면에 사용
if (isPending) return <div>Loading...</div>; - isError (status == 'error')
- 요청 중 에러가 발생한 상태 - isSuccess (status == 'success')
- 데이터 요청이 성공적으로 완료된 상태
이 외에도 보조적인 상태 정보 제공:
- error: isError 상태일 때 에러 메시지를 담고 있음
- data: isSuccess 상태일 때 받아온 데이터를 담고 있음
- isFetching: 데이터가 백그라운드에서 갱신(refetch) 중인지 여부를 나타냄 (언제든 true가 될 수 있음)
function Todos() {
const { isPending, isError, data, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodoList,
})
if (isPending) {
return <span>Loading...</span>
}
if (isError) {
return <span>Error: {error.message}</span>
}
// We can assume by this point that `isSuccess === true`
return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}
Mutation
데이터를 update/delete/create 할 때, 또는 사이드 이펙트 처리 시사용
const mutation = useMutation({
mutationFn: (newTodo) => {
return axios.post('/todos', newTodo)
},
})
- 반환값은 useQuery와 같고 첫번째 파라미터에 비동기 함수가 들어가고, 두번째 파라미터에 상황별 분기 설정이 들어간다.
- 실제 사용 시에는 mutation.mutate 메서드를 사용하고, 첫 번째 인자로 API 호출 시에 전달해야하는 데이터를 넣어주면 된다
뮤테이션은 다음과 같은 상태를 가질 수 있다.
- isIdle(status === 'idle')
뮤테이션이 대기 중이거나 초기화된 상태 - isPending(status === 'pending)
뮤테이션이 현재 실행 중인 상태 - isError(status==='error')
뮤테이션 실행 중 오류가 발생한 상태 - isSuccess(status='success')
뮤테이션이 성공적으로 완료되어 데이터를 사용할 수 있는 상태
Invalidation
쿼리를 강제로 stale(만료) 상태로 만들고 필요시 자동으로 다시 가져오게 하는 queryClient의 메서드
// 모든 쿼리 무효화
queryClient.invalidateQueries()
// 키값이 `todos`인 쿼리 무효화
queryClient.invalidateQueries({ queryKey: ['todos'] })
- 쿼리를 stale 상태로 표시, 이때 기존의 staleTime 설정은 무시된다.
- 화면에 렌더링 중인 쿼리는 자동으로 백그라운드에서 refetch
- queryKey, exact, predicate 등 다양한 옵션으로 원하는 쿼리만 세밀하게 무효화할 수 있다.
주의 사항 (중요)
시간이 없어 gpt의 공식 문서 정리글로 대체합니다.. (수정 예정)
🚀 TanStack Query의 기본 동작 방식과 설정 변경 방법
TanStack Query는 기본적으로 공격적이지만 합리적인(defaults) 설정을 가지고 있어요.
이 설정을 잘 이해하면 원치 않는 동작을 방지하고, 더 효율적으로 활용할 수 있어요!
✅ 1. 기본적으로 캐시된 데이터는 'stale(오래됨)' 상태로 간주됨
- useQuery나 useInfiniteQuery를 사용할 때,
캐시된 데이터라도 기본적으로 "오래된(stale)" 상태로 간주돼서 자동으로 다시 가져오려 해요. - 이를 변경하려면 staleTime 옵션을 조정하면 돼요!
- staleTime을 길게 설정하면, 쿼리가 데이터를 자주 새로 가져오지 않음
- staleTime: Infinity로 설정하면, 데이터를 항상 최신이라고 간주(재요청 X)
const { data } = useQuery(['posts'], fetchPosts, { staleTime: 1000 * 60 * 5 }); // 5분 동안은 새로 안 가져옴
✅ 2. 쿼리는 특정 조건에서 자동으로 다시 요청됨
다음 상황이 발생하면 자동으로 데이터를 다시 가져옴(refetch):
- 새로운 useQuery 인스턴스가 마운트될 때
- 사용자가 창을 다시 포커스했을 때 (ex. 다른 탭 갔다가 돌아옴)
- 네트워크가 재연결되었을 때 (ex. Wi-Fi 다시 연결)
- refetchInterval을 설정한 경우 주기적으로 데이터 요청
👉 이 기능을 비활성화하거나 변경하려면?
- refetchOnMount, refetchOnWindowFocus, refetchOnReconnect, refetchInterval 옵션을 조정하면 돼요!
const { data } = useQuery(['posts'], fetchPosts, {
refetchOnMount: false, // 마운트될 때 다시 요청 안 함
refetchOnWindowFocus: false, // 창을 다시 봐도 요청 안 함
refetchOnReconnect: true, // 네트워크 연결 시 다시 요청 (기본값)
refetchInterval: 1000 * 60 // 1분마다 자동 새로고침
});
✅ 3. 사용되지 않는 쿼리는 '비활성(inactive)' 상태가 됨
- useQuery를 사용하지 않으면 자동으로 캐시에 저장된 채 '비활성 상태'로 전환됨
- 이 데이터는 5분 동안 유지됨 (기본값: gcTime = 1000 * 60 * 5)
- 5분이 지나면 자동으로 캐시에서 삭제됨(GC, Garbage Collection)
👉 더 오래 유지하거나 즉시 삭제하고 싶다면?
const queryClient = new QueryClient({
defaultOptions: {
queries: {
gcTime: 1000 * 60 * 10, // 10분 동안 캐시 유지
},
},
});
✅ 4. 실패한 쿼리는 기본적으로 3번 자동 재시도됨
- 네트워크 문제 등으로 쿼리가 실패하면 3번 자동으로 재시도됨
- 재시도할 때마다 점점 긴 시간(지수 증가) 후에 다시 요청 (ex. 1초 → 2초 → 4초)
👉 재시도 횟수 변경하기
const { data } = useQuery(['posts'], fetchPosts, {
retry: 5, // 5번 재시도
retryDelay: attempt => Math.min(1000 * 2 ** attempt, 30000) // 지수 백오프 (최대 30초)
});
👉 재시도를 비활성화하고 싶다면?
const { data } = useQuery(['posts'], fetchPosts, { retry: false });
✅ 5. TanStack Query는 '구조적 공유(Structural Sharing)'을 이용해 성능 최적화
- 기본적으로 TanStack Query는 기존 데이터와 새 데이터를 비교해서
변경되지 않았다면 같은 데이터 참조 유지 (불필요한 렌더링 방지) - 이 기능 덕분에 useMemo나 useCallback을 사용할 필요가 줄어듦
👉 대부분의 경우 이 기능이 성능 최적화에 도움되므로 꺼둘 필요 없음
👉 JSON이 아닌 데이터를 비교하려면 structuralSharing 옵션을 설정할 수 있음
const queryClient = new QueryClient({
defaultOptions: {
queries: {
structuralSharing: false, // 구조적 공유 비활성화
},
},
});
'React' 카테고리의 다른 글
| 리액트 서버 컴포넌트 톺아보기 (번역) (0) | 2025.04.03 |
|---|---|
| useState의 렌더링 방식 이해하기(feat.batching) (0) | 2025.02.06 |
| Redux 정리 (0) | 2025.01.21 |
| state를 reducer로 작성하기 (0) | 2025.01.20 |
| useRef와 권장사항 (1) | 2024.11.01 |