리액트쿼리는 실무에서도 꾸준히 사용하고 있던 라이브러리다. 특히 서버상태를 관리하는데있어서 캐싱, 재시도, 네트워크 상태, 백그라운드 업데이트 등 편리한 기능들을 많이 제공한다.
리액트에서 데이터 패칭
리액트에서 데이터를 패칭하는 방법은 다양하다.
기본적으로 fetch API를 사용하거나, axios와 같은 라이브러리를 사용할 수 있다.
또한, 상태 관리 라이브러리인 Redux나 MobX를 사용하여 데이터를 관리할 수도 있다.
하지만 이러한 방법들은 데이터 패칭과 상태 관리를 직접 구현해야 하기 때문에 번거로울 수 있다.
리액트 쿼리는 이러한 문제를 해결하기 위해 만들어진 라이브러리다.
import { useState, useEffect } from 'react';
function App() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('https://api.example.com/data')
.then((response) => response.json())
.then((data) => {
setData(data);
setLoading(false);
})
.catch((error) => {
setError(error);
setLoading(false);
});
}, []);
if (loading) {
return <div>Loading...</div>;
}
if (error) {
return <div>Error: {error.message}</div>;
}
return <div>Data: {JSON.stringify(data)}</div>;
}
위 예제는 fetch API를 사용하여 데이터를 패칭하는 간단한 리액트 컴포넌트다.
데이터를 패칭하는 동안 로딩 상태를 관리하고, 에러가 발생했을 때 에러 상태를 관리한다.
하지만 이 코드는 반복적이고, 상태 관리를 직접 구현해야 하기 때문에 번거로울 수 있다.
리액트 쿼리
리액트 쿼리를 사용하면 데이터 패칭과 상태 관리를 간단하게 할 수 있다.
리액트 쿼리는 useQuery 훅을 제공하여 데이터를 패칭하고, 로딩 상태와 에러 상태를 자동으로 관리해준다.
import { useQuery } from '@tanstack/react-query';
function App() {
const { data, error, isLoading } = useQuery({
queryKey: ['data'],
queryFn: () =>
fetch('https://api.example.com/data').then((response) =>
response.json()
),
staleTime: 60000,
cacheTime: 300000,
retry: 3,
});
if (isLoading) {
return <div>Loading...</div>;
}
if (error) {
return <div>Error: {error.message}</div>;
}
return <div>Data: {JSON.stringify(data)}</div>;
}
위 예제는 리액트 쿼리를 사용하여 데이터를 패칭하는 간단한 리액트 컴포넌트다.
useQuery 훅을 사용하여 데이터를 패칭하고, 로딩 상태와 에러 상태를 자동으로 관리한다.
또한, staleTime, cacheTime, retry 등의 옵션을 사용하여 캐싱과 재시도 정책을 설정할 수 있다.
useQuery
useQuery 훅은 리액트 쿼리에서 가장 많이 사용되는 훅 중 하나다.
이 훅은 데이터를 패칭하고, 로딩 상태와 에러 상태를 자동으로 관리해준다.
주요 옵션 정리
queryKey: 쿼리를 식별하는 고유한 키, 쿼리키는 배열형태로 사용한다.- 배열의 이후 요소들을 이용해 쿼리키를 동적으로 생성할 수 있다.
useQuery({ queryKey: ['todos', { status, page }], ... })
useQuery({ queryKey: ['todos', { page, status }], ...})
useQuery({ queryKey: ['todos', { page, status, other: undefined }], ... })
이 쿼리키는 모두 동일한 것으로 간주되지만,
useQuery({ queryKey: ['todos', status, page], ... })
useQuery({ queryKey: ['todos', page, status], ...})
useQuery({ queryKey: ['todos', undefined, page, status], ...})
이 쿼리키는 모두 다른 것으로 간주된다. 배열 항목의 순서가 중요하다!
queryFn: 데이터를 패칭하는 함수, 이 함수는 프로미스를 반환해야 한다.staleTime- 데이터가 fresh한 상태에서 stale한 상태로 전환되기까지의 시간(밀리초 단위)을 설정한다.
- 기본값은 0으로, 데이터가 즉시 stale 상태로 전환된다.
- 설정한 시간 동안은 백그라운드에서 데이터를 다시 패칭하지 않고, 기존에 캐싱된 데이터를 사용한다.
- 이후에는 데이터가 stale 상태로 전환되어, 컴포넌트가 다시 마운트되거나 포커스가 돌아올 때 백그라운드에서 데이터를 다시 패칭한다.
gcTime- 캐시된 데이터가 메모리에서 제거되기까지의 시간(밀리초 단위)을 설정한다.
- 기본값은 5분(300000ms)이다.
- 캐시된 데이터가 더 이상 사용되지 않을 때(inactive한 상태가 되었을 때), 설정한 시간이 지나면 메모리에서 제거된다.
// 예시
// 대규모 리스트나 데이터 변화가 적은 경우
staleTime: 5 * 60 * 1000, // 5분 동안 fresh
gcTime: 30 * 60 * 1000, // 메모리 30분 유지
// 자주 변경되는 데이터나 실시간 데이터
staleTime: 0,
gcTime: 60 * 1000,
// 자주 변하지 않는 데이터
staleTime: Infinity, // 사실상 refetch 없음
gcTime: Infinity, // 캐시를 항상 유지
retry: 쿼리가 실패했을 때 재시도할 횟수, 기본값은 3이다.false로 설정하면 재시도를 하지 않는다.enabled: 쿼리의 활성화 여부를 설정한다. 기본값은true이다.false로 설정하면 쿼리가 자동으로 실행되지 않는다.- 이 옵션은 초기 마운트 시점에만 적용된다. 이후에는
refetch함수를 호출하여 쿼리를 수동으로 실행할 수 있다.
- 이 옵션은 초기 마운트 시점에만 적용된다. 이후에는
사용팁
리액트쿼리는 다양한 API를 제공한다. useMutation, useInfiniteQuery, useQueryClient 등을 통해 활용하는 방법이 있다.
useMutation
useMutation 훅은 데이터를 변경하는 작업(POST, PUT, DELETE 등)을 수행할 때 사용된다.
이 훅은 데이터 변경 작업의 상태(로딩, 성공, 에러 등)를 관리해준다.
import { useMutation, useQueryClient } from '@tanstack/react-query';
function App() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (newData) =>
fetch('https://api.example.com/data', {
method: 'POST',
body: JSON.stringify(newData),
}).then((response) => response.json()),
onSuccess: () => {
// 데이터 변경 후 쿼리 무효화
queryClient.invalidateQueries({ queryKey: ['data'] });
},
});
const handleAddData = () => {
mutation.mutate({ name: 'New Data' });
};
return (
<div>
<button onClick={handleAddData}>Add Data</button>
{mutation.isLoading && <div>Adding data...</div>}
{mutation.isError && <div>Error: {mutation.error.message}</div>}
{mutation.isSuccess && <div>Data added successfully!</div>}
</div>
);
}
위 예제는 useMutation 훅을 사용하여 데이터를 추가하는 간단한 리액트 컴포넌트다.
데이터 추가 작업의 상태를 관리하고, 성공 시 관련 쿼리를 무효화하여 데이터를 다시 패칭한다.
useQueryClient
useQueryClient 훅은 쿼리 클라이언트 인스턴스에 접근할 수 있게 해준다.
이를 통해 쿼리 무효화, 캐시 업데이트 등의 작업을 수행할 수 있다.
invalidateQueries
invalidateQueries 메서드는 특정 쿼리를 무효화하여 데이터를 다시 패칭하도록 할 때 사용된다.
import { useQueryClient } from '@tanstack/react-query';
function App() {
const queryClient = useQueryClient();
const invalidateData = () => {
queryClient.invalidateQueries({ queryKey: ['data'] });
};
return (
<div>
<button onClick={invalidateData}>Invalidate Data</button>
</div>
);
}
위 예제는 useQueryClient 훅을 사용하여 쿼리를 무효화하는 간단한 리액트 컴포넌트다.
버튼을 클릭하면 해당 쿼리가 무효화되어 데이터를 다시 패칭한다.
queryKey: 무효화할 쿼리의 키를 지정한다.exact:true로 설정하면 정확히 일치하는 쿼리만 무효화한다. 기본값은false이다.refetchType: 무효화 후 데이터를 다시 패칭할지 여부를 지정한다.'all','active','inactive','none'중 하나를 선택할 수 있다. 기본값은'active'이다.
setQueryData
setQueryData 메서드는 쿼리의 캐시된 데이터를 직접 업데이트할 때 사용된다.
이를 통해 서버로부터 데이터를 다시 패칭하지 않고도 UI를 즉시 업데이트할 수 있다.
import { useQueryClient } from '@tanstack/react-query';
function App() {
const queryClient = useQueryClient();
const updateData = () => {
queryClient.setQueryData(['data'], (oldData) => ({
...oldData,
newField: 'New Value',
}));
};
return (
<div>
<button onClick={updateData}>Update Data</button>
</div>
);
}
위 예제는 setQueryData 메서드를 사용하여 쿼리의 캐시된 데이터를 직접 업데이트하는 간단한 리액트 컴포넌트다.
-> setQueriesData 와의 차이점은 단일 쿼리를 업데이트할 때는 setQueryData를 사용하고, 여러 쿼리를 한꺼번에 업데이트할 때는 setQueriesData를 사용한다.
// 단일 쿼리 업데이트
queryClient.setQueryData(['data'], newData);
// 여러 쿼리 업데이트
queryClient.setQueriesData({
queryKey: ['data'] // 이 prefix를 가진 모든 쿼리
}, newData);
ErrorBoundary
리액트 쿼리는 ErrorBoundary와 함께 오류 발생 시 다시 시도할 것인지 결정할 수 있는 기능을 제공한다.
이를 통해 에러가 발생했을 때 사용자에게 적절한 피드백을 제공할 수 있다.
import { QueryErrorResetBoundary } from '@tanstack/react-query'
import { ErrorBoundary } from 'react-error-boundary'
const App = () => (
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary
onReset={reset}
fallbackRender={({ resetErrorBoundary }) => (
<div>
There was an error!
<Button onClick={() => resetErrorBoundary()}>Try again</Button>
</div>
)}
>
<Page />
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
)
// or
import { useQueryErrorResetBoundary } from '@tanstack/react-query'
import { ErrorBoundary } from 'react-error-boundary'
const App = () => {
const { reset } = useQueryErrorResetBoundary()
return (
<ErrorBoundary
onReset={reset}
fallbackRender={({ resetErrorBoundary }) => (
<div>
There was an error!
<Button onClick={() => resetErrorBoundary()}>Try again</Button>
</div>
)}
>
<Page />
</ErrorBoundary>
)
}
Devtools
리액트 쿼리는 개발자 도구를 제공하여 쿼리 상태를 시각적으로 확인할 수 있다. 이를 통해 쿼리의 상태, 캐시된 데이터, 쿼리 키 등을 쉽게 확인할 수 있다.
npm i @tanstack/react-query-devtools
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* The rest of your application */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
마치며
리액트 쿼리는 현재에도 실무에서 사용하고 있는 라이브러리이다. staleTime, gcTime에 대한 정확한 이해와 활용법을 익히면 캐싱 전략을 효과적으로 세울 수 있을 것이다. 무조건 고정된 값이 아닌 데이터 특성에 맞게 적절히 조절하는 것이 중요할것 같다. 또 무한스크롤, 낙관적업데이트, Nextjs에서의 스트리밍과 프리패치 등 다양한 기능들도 제공하고 있어서 이를 활용하면 보다 더 애플리케이션을 가볍고 빠르게 만들 수 있을 것이다.