Tanstack Query (React-query) 왜 써야할까?
요약
Tanstack Query는 HTTP 요청을 전송하고 Frontend 사용자 인터페이스를 벡엔드 데이터와 동기화된 상태로 유지하는 데 이용하는 라이브러리이다.
물론 이 작업을 할때 useEffect와 fetch 함수로도 할 수 있다.
근데 useEffect, useState 같은 리액트에 내장된 기능을 이용하고
브라우저에 내장된 fetch 함수를 이용한다면 상태 관리, useEffect 훅 작성을 위해 많은 양의 코드를 작성 해야한다.
근데 Tanstack Query를 이용하면 코드가 매우 간결해져서 훨씬 수월하게 작업할 수 있다.
-> 여러 가지의 기능이 기본적으로 내장 되어 있기 때문이다.
이 Tanstack Query 라이브러리를 이용하면 상태 관리, 캐시 처리, 자체적으로 처리되는 데이터 가져오기 등
앱을 좀 더 효율적으로 짧은 코드로 만들어 모든 기능을 이용할 수 있다.
개념에 대해 자세히 살펴보자면...
공식 문서의 설명은 " fetching, caching, 서버 데이터와의 동기화를 지원해주는 라이브러리" 이다.
그 밖의 설명은
1. React Query는 React Application에서 서버 상태를 불러오고, 캐싱하며, 지속적으로 동기화하고 업데이트하는 작업을 도와주는 라이브러리입니다.
2. 복잡하고 장황한 코드가 필요한 다른 데이터 불러오기 방식과 달리 React Component 내부에서 간단하고 직관적으로 API를 사용할 수 있습니다.
3. 더 나아가 React Query에서 제공하는 캐싱, Window Focus Refetching 등 다양한 기능을 활용하여 API 요청과 관련된 번잡한 작업 없이 “핵심 로직”에 집중할 수 있습니다.
react-query의 장점 중 하나는 caching이다.여기서 캐싱(caching)은 특정한 데이터의 복사본을 저장하여 이후 동일한 데이터에 재접근시 속도를 높히는 것을 뜻한다.
이 캐싱을 통해서 반복적 비동기 데이터 호출을 방지하고, 불필요한 API call을 줄여서 서버 부하를 줄여준다.
그렇다면 적절하게 데이터를 갱신해 줄 수 있다는 말과 같은데 그 상황은 언제가 좋을까?
- 화면을 보고 있을 때
- 페이지의 전환이 일어났을 때
- 페이지 전환 없이 이벤트가 발생해 데이터를 요청할 때
이 정도 경우의 상황에 맞다고 볼 수 있다. 이 상황을 위해 React-Query에서는 기본적인 옵션을 제공하는데
refetchOnWindowFocus, //default: true
refetchOnMount, //default: true
refetchOnReconnect, //default: true
staleTime, //default: 0
cacheTime, //default: 5분 (60 * 5 * 1000)
위의 옵션들을 통해 우리는 React-Query가 어떤 시점에 데이터를 Refetching하는지 알 수 있다.
- 브라우저에 포커스가 들어온 경우(refetchOnWindowFocus)
- 새로운 컴포넌트 마운트가 발생한 경우(refetchOnMount)
- 네트워크 재연결이 발생한 경우(refetchOnReconnect)
React-query에서 data fetching 하기 위해 제공되는 대표적인 기능
useQuery : GET
useMutation : PUT, UPDATE, DELETE
useQuery
- 첫 번째 파라미터로 unique key를 포함한 배열이 들어간다. 이후 동일한 쿼리를 불러올 때 유용하게 사용된다.
- 첫 번째 파라미터에 들어가는 배열의 첫 요소는 unique key로 사용되고, 두 번째 요소부터는 query 함수 내부의 파라미터로 값들이 전달된다.
- 두 번째 파라미터로 실제 호출하고자 하는 비동기 함수가 들어간다. 이때 함수는 Promise를 반환하는 형태여야 한다.
- 최종 반환 값은 API의 성공, 실패 여부, 반환값을 포함한 객체이다.
isPending을 통하여 로딩 여부, isError를 통해 에러 발생 여부, data로 성공 시 데이터 반환 할 수 있다.
useQuery를 enabled 옵션 사용으로 동기적으로 실행 가능하다.
- useQuery의 세 번째 인자로 다양한 옵션 값들이 들어가는데, 여기서 enabled에 값을 대입하면 해당 값이 true일 때 useQuery를 동기적으로 실행한다!
const { data: todoList, error, isFetching } = useQuery("todos", fetchTodoList);
const { data: nextTodo, error, isFetching } = useQuery(
"nextTodos",
fetchNextTodoList,
{
enabled: !!todoList // true가 되면 fetchNextTodoList를 실행한다
}
);
useMutation : query와 달리 mutations는 데이터를 create, update, delete하거나 서버의 side-effects를 수행할 때 사용한다.
function App() {
const mutation = useMutation({
mutationFn: (newTodo) => {
return axios.post('/todos', newTodo)
},
})
return (
<div>
{mutation.isLoading ? (
'Adding todo...'
) : (
<>
{mutation.isError ? (
<div>An error occurred: {mutation.error.message}</div>
) : null}
{mutation.isSuccess ? <div>Todo added!</div> : null}
<button
onClick={() => {
mutation.mutate({ id: new Date(), title: 'Do Laundry' })
}}
>
Create Todo
</button>
</>
)}
</div>
)
}
- 위의 코드에서 볼 수 있듯이 반환값은 useQuery와 동일하지만, 처음 사용 시에 post 비동기 함수를 넣어주었다. 이때 useMutation의 첫 번째 파라미터에 비동기 함수가 들어가고, 두 번째 인자로 상황 별 분기 설정이 들어간다는 점이 차이이다.
- 실제 사용 시에는 mutation.mutate 메서드를 사용하고, 첫 번째 인자로 API 호출 시에 전달해주어야하는 데이터를 넣어주면 된다.
QueryCache
- 포함된 모든 데이터, 메타 정보 및 쿼리 상태를 저장한다.
- onError, onSuccess 콜백을 사용하여 애플리케이션 전역에서 이벤트를 핸들링한다.
import { QueryCache } from '@tanstack/react-query'
const queryCache = new QueryCache({
onError: error => {
console.log(error)
},
onSuccess: data => {
console.log(data)
}
})
const query = queryCache.find({ queryKey: ['posts'] })
몇 가지 메소드 들을 살펴보면
1. queryCache.find
캐시에서 기존 쿼리 인스턴스를 가져오는 데 사용하고, 쿼리가 존재하지 않을 때 undefined 가 반환한다.
const query = queryCache.find({ queryKey })
2. queryCache.findAll
queryKey가 부분적으로 일치하는 모든 쿼리 인스턴스를 가져오고, 쿼리가 존재하지 않을 때 빈 배열 반환한다.
const queries = queryCache.findAll({ queryKey })
3. queryCache.subscribe
쿼리 캐시 전체를 구독하고 쿼리 상태가 변경되거나 업데이트, 추가 또는 제거시 안전하게 알림을 받을 수 있다.
옵션 -> callback 함수 (캐시 업데이트시 해당 쿼리 캐시와 함께 호출) , 반환 -> unsubscribe 함수 (해당 쿼리 캐시에 대한 콜백 구독 취소)
const callback = event => {
console.log(event.type, event.query)
}
const unsubscribe = queryCache.subscribe(callback)
4. queryCache.clear
캐시를 모두 지우고 새롭게 시작
queryCache.clear()