다운로드 UI가 있거나 UX를 저해하는 불필요한 네트워크 요청을 제거할 때 사용.
대용량 fetching을 중간에 취소하거나, 사용하지 않는 컴포넌트에서 fetching이 진행 중이면 자동으로 취소해 불필요한 네트워크 비용을 절감함.
QueryFunctionContext
queryFn 은 매개변수로 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) => {}}
페이지 컴포넌트 unmount 시 Query 취소
API 요청의 기본 설정은 컴포넌트가 unmount 되어도 네트워크 요청이 중단되지 않음.
GET 요청은 abort signal이 옵션으로 전달된 경우에만 unmount 시 자동으로 네트워크 요청이 취소됨.
import axios from 'axios'
const query = useQuery({
queryKey: ['todos'],
queryFn: ({ signal }) =>
axios.get('/todos', {
// Pass the signal to `axios`
signal,
}),
})
수동으로 Query 취소
queryClient.cancelQueries 사용해 특정 쿼리 취소 가능.
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>
)
모든 GET 요청마다 Abort Signal 적용 필요 여부
불필요한 네트워크 요청을 최소화한다는 명분으로, 단순히 모든 GET 요청에 Abort Signal을 적용하는 것은 권장되지 않음.
동영상 다운로드처럼 대용량 fetching이 아닌 이상, 대부분의 GET 요청은 빠르게 완료되고 캐싱 처리되어 성능에 유의미한 영향을 주지 못함.
대용량 fetching이 있거나 Optimistic UI를 구현하는 경우처럼 필요한 상황에서만 적용하는 것을 권장.
서버 요청이 성공할 것을 가정하고 UI를 먼저 변경한 뒤, 서버 요청을 보내는 방식.
요청이 실패하면 UI를 원상복구(revert/rollback)해야 함. 즉각적인 사용자 피드백을 제공할 수 있어 UX 향상에 효과적.
페이지 이동 전에 이동할 페이지의 쿼리를 백그라운드에서 미리 호출(prefetching).
캐시 데이터가 있는 상태에서 해당 페이지로 이동하면 로딩 없이 바로 UI를 볼 수 있음.
const prefetchTodos = async () => {
// 이동할 페이지의 queryKey, queryFn과 동일하게 설정 필요
await queryClient.prefetchQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
})
}
페이지 이동 시 매번 Loading UI를 표시하는 대신, 기존 UI를 유지하다가 서버에서 새로운 데이터를 받아온 뒤에 화면을 갱신하는 방식을 적용.
useQuery 옵션에서 keepPreviousData: true를 설정하면 이전 캐시 데이터를 기준으로 isLoading 여부를 판단하므로, 페이지 전환 시 UX를 더 부드럽게 구성할 수 있음.
데이터 fetching이 일어날 때마다 기존 리스트 데이터에 fetching된 데이터를 추가하고 싶을 때 유용하게 사용할 수 있는 훅. 더보기 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, pageParams } 형태로 데이터 등록getNextPageParam 실행nextPageParam을 훅 내부 메모리에 저장 (캐시 저장 아님)nextPageParam이 undefined가 아닐 경우 hasNextPage를 true로 상태 변경fetchNextPage 실행nextPageParam을 queryFn의 매개변수로 전달useQuery는 queryFn의 반환값이 그대로 캐시 데이터로 등록됨.useInfiniteQuery는 queryFn의 반환값이 pages 배열 요소로 추가됨.pageParam은 pageParams 배열 요소로 추가됨.useMemo와 같은 메모이제이션 적용을 고려해야 함.