React Query
는 서버 데이터를 가져와 캐싱, 동기화, 업데이트와 같은 기능들을 쉽게 사용할 수 있게 도와주는 라이브러리입니다.
React Qeury
를 사용하면 아래와 같은 기능들을 직접 구현하지 않고 쉽고 빠르게 사용할 수 있습니다.
클라이언트에서 사용하는 상태와 서버에서 가져온 상태를 혼합하여 사용할 경우 상태 관리와 로직 작성이 복잡해지고 유지보수가 어려워질 수 있는데 이러한 경우에
React Query
를 사용해 서버 상태를 따로 관리하면 프로젝트 관리가 편해집니다.
쉽게 최신 상태를 가져오는 기능을 제공합니다. 일정 시간마다 상태를 업데이트하거나 특정 동작 이후에 상태를 업데이트할 수 있습니다.
API 요청 결과를 캐싱하여 관리합니다. 서버에 API 요청하여 받아온 결과를 캐싱하며 중복 요청을 최소화할 수 있습니다.
비동기 API 요청에 대한 로딩 상태, 결과 값, 에러 상태와 같은 여러가지 상태를 확인하는 기능을 제공합니다.
React Query
를 사용하면 단순 기능 구현, 기능 테스트, 오류 수정과 같은 과정에서 많은 개발 인력 및 시간을 감소시킬 수 있습니다.
다음은 게시글의 정보를 불러오기 위해 서버에 GET method
요청을 보내는 과정입니다.
const [posts, setPosts] = useState(null);
// 사용자에게 로딩 UI를 보여주기 위한 loading 상태
const [loading, setLoading] = useState(false);
// 에러가 발생했을 때 사용자에게 UI를 보여주기 위한 error 상태
const [error, setError] = useState(false);
const fetchPosts = async () => {
if (loading) return;
setLoading(true);
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
setPosts(response.data);
} catch (error) {
setError(true)
}
setIsLoading(false);
};
위와 같이 간단하게 게시글 정보를 불러오는 작업만으로도 많은 코드를 작성해야 합니다.
물론 해당 로직의 재활용성을 높이기 위해 custom hook
으로 작성할 수도 있지만 더 자세한 코드를 작성해야 하고, 직접 테스트하는 것에도 많은 시간이 투자됩니다.
반면 React Query
는 위와 같이 복잡한 로직을 다음과 같이 더 쉽고 간결하게 표현할 수 있습니다.
const fetchPosts = async () => await axios.get('https://jsonplaceholder.typicode.com/posts').then(res => res.data);
const { data, isLoading, error } = useQuery(['fetchPosts'], fetchPosts);
라이프사이클(Lifecycle)
은 생명주기를 뜻하며 브라우저 상에서 나타나고, 업데이트되고, 사라지게 되는 과정을 의미합니다.
React Query
에서는 아래와 같은 라이프사이클 상태가 존재합니다.
데이터가 만료되지 않은 신선한 상태
컴포넌트의 상태가 변경되도, 데이터를 다시 요청하지 않는다.
데이터가 만료된 상태
클라이언트로 전달된 서버 데이터는 다른 유저가 CRUD연산을 적용할 수 있기 때문에 데이터 최신화가 필요하여 컴포넌트의 마운트, 업데이트시 데이터를 재요청한다.
fresh
에서 stale
로 넘어가는 시간의 Default값은 0ms이다.
사용하지 않는 상태로 일정 시간이 지나면 가비지 컬렉터(Garbage Collection, 쓰레기 수집)
가 캐시에서 제거한다.
Default값은 5000ms(5분)으로 만약 해당 값을 무한대(infinite)
로 설정하면 데이터 누수가 심해지고, 오류가 발생하여 권장하지 않는다.
가비지 컬렉터(Garbage Collection, 쓰레기 수집)
에 의해 캐시에서 제거된 상태사용자에게 컴포넌트가 보여질 때 A 쿼리 인스턴스를 서버에 요청한다.
서버에서 데이터를 가져온 후 해당 데이터를 브라우저에 캐싱한다.
캐싱된 데이터는 만료되지 않는 상태에서 지정한 staleTime(Default 0ms)
이후 만료된 상태로 변경된다.
A 쿼리 인스턴스가 컴포넌트에서 사라진다.
브라우저에 저장된 캐시는 지정한 cacheTime(Default 5000ms)
이후 가비지 컬렉터에 의해 수집되어 삭제된다.
만약
cacheTime
이 지나기 전에 A 쿼리 인스턴스가 새롭게 요청되면, 데이터를 요청하고, 데이터를 새로 가져오기전까지는 이미 브라우저에 캐시 된 데이터를 보여준다.
데이터 요청 후 만료되기 까지 걸리는 시간
StaleTime
은 데이터가 fresh
에서 stale
상태로 변경되는 데 걸리는 시간을 뜻한다.
fresh
상태일 때는 쿼리 인스턴스가 새롭게 mount
되어도, 네트워크 fetch
가 일어나지 않는다.
즉 데이터가 만료 되기 전까지는 쿼리를 새롭게 실행시켜도 새로운 요청을 하지 않는다.
데이터를 사용하지 않은 상태
데이터가 inactive
된 상태로 캐싱되어 남아있는 시간을 의미한다.
쿼리 인스턴스가 unmount
되면, 데이터는 inactive
상태로 변경된다.
즉 쿼리 요청 후 쿼리 요청 작업이 끝난 데이터는 사용하지 않은 상태로 변경된다.
캐시는 cacheTime
만큼 유지되며, 기본값은 5000ms(5분)
으로 cacheTime
이 지나면 가비지 콜렉터(Garbage Collection)
에 수집된다.
만약 cacheTime
이 지나기 전에 쿼리 인스턴스가 다시 마운트 되면, 데이터를 fetch
하는 동안 캐시 데이터를 보여준다.
아래 명령어를 통해 React Query
를 설치한다.
npm i @tanstack/react-query
yarn add @tanstack/react-query
이 후 프로젝트에서 사용하기 위해 QueryClientProvider
로 컴포넌트를 감싼 뒤QueryClient
인스턴스를 생성해서 props
로 전달한다.
const queryClinet = new QueryClient()
root.render(
<React.StrictMode>
<QueryClientProvider client={queryClinet}>
<App />
</QueryClientProvider>
</React.StrictMode>
);
React Query
로 데이터를 가져오기 위해서는 useQuery
를 사용해야 하며 Query key
, API를 요청하는 비동기 함수, Query Options
를 순서대로 인자로 넣어 사용할 수 있습니다.
이때 첫 번째 인자인 Query key
는 캐싱 처리 및 쿼리 호출에 사용되기 때문에 중복되지 않게 관리해야 합니다.
const fetchPosts = async () => await axios.get('https://jsonplaceholder.typicode.com/posts').then(res => res.data);
const { data, isLoading, error } = useQuery(['fetchPosts'], fetchPosts);
useQuery
가 반환하는 return
값의 종류는 다음과 같습니다.
Status
idle
: 초기 상태loading
: 데이터fetching
중일 때의 상태success
: 데이터fetch
에 성공한 상태error
: 데이터fetch
에 실패한 상태
isFetcing
- 데이터가
fetch
상태일 때 true를 반환- 캐싱 데이터가 존재하여 백그라운드에서
fetch
되어도 true를 반환
isLoading
- 캐싱된 데이터가 없을 때 또는
fetch
중인 경우 true를 반환
data
- 응답받은 데이터
error
- 실패 정보
refetch
- 수동으로 데이터
refetch
를 실행하는 함수scale
이나cache
같은 설정들이 무시되고, 무조건 데이터를 다시fetching
useQuery
에서 세번째 인자로 사용되는 Option 종류는 다음과 같습니다.
enabled (boolean)
default
값은 true이며, 쿼리의 자동 실행여부를 설정하는 옵션
retry (boolean | number | (failureCount: number, error: TError) => boolean)
retry
는 실패한 쿼리를 재시도하는 옵션으로 기본적으로 쿼리 실패시 3번 재시도- true 로 설정하면 쿼리 실패시 무한 재시도하고 false로 설정하면 재시도를 하지 않는다.
staleTime (number | Infinity)
staleTime
은 데이터가fresh
상태로 유지되는 시간이다. 해당 시간이 지나면stale
상태가 된다.default staleTime
은 0ms 이다.fresh
상태에서는 쿼리가 다시mount
되어도fetch
가 실행되지 않는다.
cacheTime (number | Infinity)
cacheTime
은inactive
상태인 캐시 데이터가 메모리에 남아있는 시간이다. 이 시간이 지나면 캐시 데이터는 가비지 컬렉터에 의해 메모리에서 제거된다.default cacheTime
은 5000ms(5분)이다.
onSuccess ((data: TDdata) => void)
onSuccess
는 쿼리 성공 시 실행되는 함수이다.- 매개변수 data는 성공 시 서버에서 넘어오는
response
값이다.
onError ((error: TError) => void)
onError
는 쿼리 실패 시 실행되는 함수이다.- 매개변수로 에러 값을 받을 수 있다.
onSettled ((data?: TData, error?: TError) => void)
onSettled
는 쿼리가 성공해서 성공한 데이터가 전달되거나, 실패해서 에러가 전달 될 때 실행되는 함수이다.- 매개변수로 성공 시엔 성공 데이터, 실패 시에는 에러가 전달된다.
select : (data : TData) ⇒ unknown
- 응답 데이터를 가공할 때 사용하는 함수이다.
initialData (TData | () => TData)
- 쿼리가 아직 생성되지 않았을 때
initialData
를 설정하면 쿼리 캐시의 초기 데이터로 사용된다.staleTime
이 설정되지 않은 경우 초기 데이터는 기본적으로stale
상태로 간주한다.
위에서 설명한 Query Options
중 enabled
옵션의 값이 true일때 useQuery
를 실행하는 특성을 이용하여 동기적으로 useQuery
를 사용할 수 있습니다.
const fetchPosts = async () => await axios.get('https://jsonplaceholder.typicode.com/posts').then(res => res.data);
const fetchUsers = async () => await axios.get('https://jsonplaceholder.typicode.com/users').then(res => res.data);
const { data: posts, isLoading, error } = useQuery(['fetchPosts'], fetchPosts);
const { data: users, isLoading, error } = useQuery(['fetchUsers'], fetchUsers, {
enabled: !!posts // true가 되면 fetchUsers를 실행
});
특정 정보를 포함하여 Query key
를 관리하고 싶다면 useQuery
의 첫 번째 인자에 배열로 파라미터를 추가할 수 있습니다.
const fetchPost = async id => await axios.get(`https://jsonplaceholder.typicode.com/post/${id}`).then(res => res.data);
const id = 1
const { data, isLoading, error } = useQuery(['fetchPost', id], () => fetchPosts(id));
useQueries
를 사용하면 여러 개의 useQuery
를 배열에 담아 간결한 로직을 작성할 수 있습니다.
const results = useQueries({
ueries: [
{ queryKey: ['fetchPost', id], queryFn: () => fetchPost(id)},
{ queryKey: ['fetchComment', id], queryFn: () => fetchComment(id)}
{ queryKey: ['fetchUsers', id], queryFn: () => fetchUsers(id)}
]
})
React Query
로 데이터를 변경하기 위해서는 useMutation
을 사용해야 하며 첫 번째 인자로 API를 요청하는 비동기 함수를, 두 번째 인자로는 옵션 데이터를 넣을 수 있습니다.
데이터를 변경할 때만 사용하기 때문에 캐싱 기능의 사용이 필요하지 않아 Query key
는 사용하지 않습니다.
아래 코드는 게시글을 등록하는 간단한 예제입니다.
const editPost = async postData => await axios.post('https://jsonplaceholder.typicode.com/post', postData)
const { data, isLoading, error } = useMutation(() => editPost(postData));
useMutation
역시 API 요청에 대한 로딩 상태, 에러 상태, 결과 등 여러 상태를 반환합니다.
일반적으로 API의 요청 이후 해당 결과를 업데이트하여 UI를 통해 사용자에게 노출시켜야 합니다.
이 때 useMutation
의 옵션을 설정해주면 기존에 사용했던 Query key
로 데이터를 쉽게 가져올 수 있습니다.
const queryClient = useQueryClient();
const editPost = async postData => await axios.post('https://jsonplaceholder.typicode.com/post', postData)
const { data, isLoading, error } = useMutation(() => editPost(postData), {
onSuccess: () => () => queryClient.invalidateQueries(["fetchPosts"]),
});
위 코드는 useMutation
의 두 번째 인자에 onSuccess
를 정의하여 데이터 변경에 성공하면 queryClient
의 invalidateQueries
를 실행합니다.
queryClient.invalidateQueries
는 인자로 Query key
를 받아 해당 key로 관리하는 API를 재호출합니다.
onSuccess
외에도 위에서 설명한 Query Options
의 다양한 옵션을 사용하면 복잡한 기능을 더 간결한 로직으로 구현할 수 있습니다.
React Query Devtools
는 React Query
와 함께 사용되는 내장 개발 도구로 사용중인 모든 쿼리의 상태를 시각화하며, 이 쿼리가 예상대로 작동하지 않는 경우 문제를 해결하는데 도움이 될 수 있습니다.
개발 도구를 실행하기 위해서는 앞서 설명한 QueryClientProvider
로 ReactQueryDevtools
를 감싸줘야 합니다.
const queryClinet = new QueryClient()
root.render(
<React.StrictMode>
<QueryClientProvider client={queryClinet}>
//initialIsOpen : open된 채로 시작
//position : devtools를 열 수 있는 logo 위치 - 우측 하단으로 지정
<ReactQueryDevtools initialIsOpen={false} position='bottom-right' />
<App />
</QueryClientProvider>
</React.StrictMode>
);
위와 같이 적용 후, 실행하면 우측 하단에 아래와 같은 꽃 모양(React Query 로고)의 버튼이 생긴 것을 확인할 수 있습니다.
해당 로고를 클릭하면, 다음과 같은 개발도구가 화면에 나타나게 됩니다.
React Query Devtools
는 다음과 같이 기능을 제공합니다.
queryKey
로 쿼리를 표시합니다.
fresh
, fetching
, stale
, inactive
와 같은 쿼리 상태를 표시합니다.
해당 API를 요청하는 observer
수 조회가 가능합니다.
쿼리가 마지막으로 업데이트 된 시간을 확인할 수 있습니다.
refetch
, invalidate
, reset
, remove
등의 actions
를 GUI를 통해 쉽게 적용할 수 있습니다.
Data Explorer
탭에서 Chrome devtools
의 Network 탭에서 존재하는 정보 또한 확인할 수 있습니다.
Window Focus Refetching | TanStack Query Docs
React Query Tutorial for Beginners - Codevolution
React Query와 상태관리 :: 2월 우아한테크세미나 - 우아한테크