1. 무엇이며, 언제 사용하나?
다운로드 UI가 있을 때, 또는 UX를 저해시키는 불필요한 네트워크 요청을 제거하기 위해 사용된다.
대용량 fetching을 중간에 취소하거나 사용하지 않는 컴포넌트에서 fetching이 진행 중이면 자동으로 취소시켜 불필요한 네트워크 비용을 줄일 수 있다.
queryFn의 매개변수로 Abort Singnal을 받을 수 있고, 이를 이용해서 Query 취소를 가능하게 한다.
2. 사용방법
(2)-1. QueryFunctionContext
export const getTodos = async (queryFnContext) => {
const { queryKey, pageParam, signal, meta } = queryFnContext;
// queryKey: 배열형태의 쿼리키
// pageParam: useInfiniteQuery 사용 시 getNextPageParam 실행 시 적용
// signal: AbortSignal 을 의미 (네트워크 요청을 중간에 중단시킬 수 있는 장치)
// meta: query에 대한 정보를 추가적으로 메모를 남길 수 있는 string 필드
const response = await axios.get("http://localhost:5000/todos", { signal });
return response.data;
};
useQuery({
queryKey: ["todos"],
queryFn: getTodos,
})
// example: <div onClick={(event) => {}}
(2)-2. 페이지 컴포넌트 unmount 시 Query 취소
import axios from 'axios'
const query = useQuery({
queryKey: ['todos'],
queryFn: ({ signal }) =>
axios.get('/todos', {
// Pass the signal to `axios`
signal,
}),
})
(2)-3. 수동으로 Query 취소
const query = useQuery({
queryKey: ['todos'],
queryFn: async ({ signal }) => {
const resp = await fetch('/todos', { signal })
return resp.json()
},
})
const queryClient = useQueryClient()
return (
<button
onClick={(e) => {
e.preventDefault()
queryClient.cancelQueries({ queryKey: ['todos'] })
}}
>
Cancel
</button>
)
3. 사용 시 주의사항
모든 GET 요청마다 Abort Signal 을 심으면 좋을까?
불필요한 네트워크 요청을 최소화한다는 명분으로 단순하게 모든 GET 요청마다 Abort Signal 을 심는 것은 작업부하를 올리기에 바람직하지 않음
동영상 다운로드 같은 대용량 fetching이 아닌 이상 대부분의 GET 요청은 빠르게 완료 및 캐싱처리되어 성능에 유의미한 영향을 끼치지 못함
대용량 fetching이 있는 경우 또는 Optimistic UI를 구현할 때 처럼 필요한 경우에만 적용하는 것을 권장함
서버 요청이 정상임을 가정하고 더 나은 UX를 제공할 수 있는 방법인 낙관적 업데이트
서버 요청이 정상적으로 잘 될 거란 가정 하에 UI 변경을 먼저 하고, 서버 요청하는 방식
혹시라도 서버 요청이 실패하는 경우, UI를 원상복구(revert / roll back)한다.

특정 데이터를 백그라운드에서 가져올 수 있는 기능
다른 페이지 클릭 시 매번 Loading UI를 보여주기보단 기존 UI를 유지하다가 서버로부터 새로운 데이터를 받아왔을 때 바꾸는 방식을 적용할 수 있음
useQuery의 옵션 중 keepPreviousData를 true로 바꾸면 이전 캐시데이터를 기반으로 isLoading 여부를 판단하게 함

(1) Infinite Queries
Data Fetching 이 일어날 때 마다 기존 리스트 데이터에 Fetched Data를 추가하고자 할 때 유용하게 사용할 수 있는 Hook
더보기 UI 또는 무한 스크롤 UI 에 사용하기에 적합

const fetchProjects = async ({ pageParam = 0 }) => {
const res = await fetch('/api/projects?cursor=' + pageParam)
return res.json()
}
const {
data,
error,
fetchNextPage,
hasNextPage,
isFetching,
isFetchingNextPage,
status,
} = useInfiniteQuery({
queryKey: ['projects'],
queryFn: fetchProjects,
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
})
실행 순서 정리
queryFn 실행 ➡️ 캐시 데이터 등록 { pages, pageParam } ➡️ getNextPageParam 실행 (리턴된 NextPageParam은 훅 내부 메모리에 저장함 캐시 저장X) ➡️ (NextPageParam 이 undefined가 아니면) hasNextPage true로 상태변경 ➡️ fetchNextPage 실행 ➡️ queryFn 실행 (이때 내부적으로 저장돼 있던 NextPageParam을 queryFn의 매개변수로 넘겨줌)

pages 와 pageParams 를 갖는 캐시 데이터
useinfiniteQuery 사용 시 주의사항