TanStack Query는 서버에서 받은 데이터의 상태관리와 캐시 처리를 보다 간편하게 할 수 있는 비동기 상태 관리 라이브러리이다.
TanStack Query는 데이터 캐싱을 통해 불필요한 네트워크 요청을 방지하고 success, loading, error의 상태를 바로 얻어 비동기 상태를 구분하기 위해 필요한 코드를 줄일 수 있다
// main.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const queryClient = new QueryClient()
createRoot(document.getElementById('root')!).render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
)
QueryClientProvider를 통해 생성자 함수를 호출하는 인스턴스 queryClient를 props로 전달해주면 하위 컴포넌트에서 QueryClient에 접근 할 수 있다.
useQuery는 서버에서 데이터를 가져올 때(GET, POST포함) 사용된다.
// app.tsx
const { data, isLoading, isError } = useQuery<return되는data타입>({
queryKey: ['data', 'read'],
queryFn: async () => {
const res = await fetch('서버주소', { 요청 할 내용 })
const data = await res.json()
return data
},
// option ...
})
url이 동적 파라미터인 경우엔 함수를 생성하여 내부에 useQuery를 작성하고 parameter를 받아 동적으로 데이터를 받을 수 있도록 한다.
const useFetchData = (id: string) => {
return useQuery<return되는data타입>({
queryKey: ['data', id ],
queryFn: async () => {
const res = await fetch(`https://example.com/data/${id}`)
const data = await res.json()
return data
}
// option ...
}
const { data, error } = useFetchData(id)
필수옵션인 queryKey는 고유한 쿼리 키(식별자)로 만약 두개의 useQuery가 각 옵션과 옵션의 내용이 달라도 queryKey가 같다면 같은 useQuery로 인식이 된다. 쿼리키만 같은 두개의 useQuery가 있을 때 두번째 쿼리는 첫번째쿼리의 캐시된 데이터를 반환하게 되어 queryKey는 비교가 가능하도록 고유한 키로 작성하여야 한다.
필수옵션인 queryFn는 데이터를 가져오는 쿼리함수로 데이터나 오류를 반환한다.
enabled는 쿼리 자동 실행 여부로 기본값은 true이기때문에 useQuery가 마운트되면 바로 실행되며 false이면 초기 마운트 시에는 실행되지 않는다.
initialData는 쿼리함수에서 반환되는 data가 있기 전에(=쿼리가 생성되기 전에) 초기데이터를 지정할 수 있다.
retry는 쿼리 실패시 재시도 횟수를 지정할 수 있는 옵션이다.
select는 쿼리함수에서 반환되는 데이터 중에서 데이터를 선택하여 가져올 함수로 queryFn에서 반환되는 data 중에서 선택된 data를 반환한다.
staleTime는 데이터를 캐싱할 필요가 있을 시 데이터가 상하는데에 걸리는 시간을 정하여 데이터를 캐싱한다. 예를들어 매요청마다 데이터(ex.시간)가 달라질 경우에 사용할 수 있다.
data 성공적으로 가져온 데이터
refetch 캐시된 데이터가 있다면 무시하고 데이터를 새로 다시 가져오는 함수이다.
isLoading 쿼리함수가 진행중 일 때 반환되는 boolean값으로 쿼리의 첫번째 가져오기가 진행 중인 경우를 나타내며 최초 한 번 동작한다. 스켈레톤UI를 보여줄 때 유용한 속성이다.
isFetching 쿼리함수가 실행 중의 여부로 초기 useQuery가 실행 될 때와 refetch시에 가져오는 중임을 나타낸다. 즉, 서버와 통신할 때 마다 실행된다.
error 오류 발생시 반환되는 객체이다.
isError 쿼리 함수에서의 오류 발생을 나타낸다.
데이터 변경 작업을 위한 useMutation 훅은 생성, 수정, 삭제 등의 변이 작업을 처리하고 요청상태 및 결과를 제공한다. (POST, PUT, DELETE)
필수옵션인 mutationFn 은 변이를 실행하는 함수이다. mutate가 호출 될때마다 실행되며 useQuery의 queryFn과 비슷한 역할을 하지만 자동으로 요청을 보내는 것이 아닌 mutate를 호출할 때만 서버에 요청을 보낸다.
onMutate mutationFn과 동시에 실행되는 함수로 비동기 로직이 끝날때까지 기다리지 않고 UI에 바뀐 데이터로 낙관적 업데이트를 해준다.
onSuccess mutation이 성공적으로 완료되면 호출되는 함수로 mutate가 반환하는 데이터와 mutation을 실행할 때 전달된 인자를 참조하여 variables로 받는다. 서버요청이 완료 된 이후 실행되기때문에 최적화를 위해 onMutate를 활용하는 것이 더 좋다.
onError mutation에서 오류가 발생하면 실행되는 함수로 mutate가 반환하는 데이터와 에러, context를 인자로 받는다. context는 낙관적 업데이트를 했을 시 사용했던 캐시된 객체 데이터이며 낙관적 업데이트 된 데이터를 복원 할 수 있다.
onSettled mutation이 성공, 실패에 상관없이 실행이 완료되면 항상 호출되는 함수이다.
export function useCreateData() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (newData) => {
const res = await fetch('...', { ... })
const data = await res.json()
return data
},
onMutate: async (newData) => {
// 낙관적 업데이트
const id = newData.id
const previousData = queryClient.getQueryData(['data', id ])
if(previousData) {
queryClient.setQueryData(['data', id ],[...previousData, newData])
}
return { previousData }
},
onSuccess: (_newData, variables) => {
const id = variables.id
queryClient.invalidateQueries({ queryKey: ['data', id ] })
},
onError: (_error, newData, context) => {
const id = newData.id
if (context?.previousData) {
queryClient.setQueryData(['data', id ], context.previousData)
}
},
})
}
const { mutate: mutateForCreate } = useCreateData()
QueryClientProvider의 props인 queryClient를 useQueryClient()훅을 통해 사용 할 수 있다. queryClient에는 캐싱된 데이터와 위의 코드에서 사용된 getQueryData
, setQueryData
, invalidateQueries
등 다양한 메서드가 들어있다.
getQueryData( ) 인자로 queryKey를 받아 캐시된 데이터를 반환한다.
setQueryData( ) 인자로 queryKey와 새로운데이터를 받아 캐시된 데이터에 새로운 데이터를 넣는다.
invalidateQueries( ) mutation이 성공 이후 기존 캐시된 데이터 쿼리를 무효화(invalidate)하여 새로 캐싱한다.