useQuery vs useInfiniteQuery in TanStack
react query 파헤치기기
TanStack
TanStack(react) Query는 서버에서 가져오는 데이터를 효율적으로 가져올 수 있다!
사용하기 전에 QueryClientProvider
로 최상단에서 감싸준 뒤 사용하면 된다.
1. useQuery
밑이 useQuery의 기능과 옵션인데 너무 길다.
const {
data,
dataUpdatedAt,
error,
errorUpdatedAt,
failureCount,
failureReason,
fetchStatus,
isError,
isFetched,
isFetchedAfterMount,
isFetching,
isInitialLoading,
isLoading,
isLoadingError,
isPaused,
isPending,
isPlaceholderData,
isRefetchError,
isRefetching,
isStale,
isSuccess,
promise,
refetch,
status,
} = useQuery(
{
queryKey, //필수
queryFn, //필수
gcTime, // 여기부터 옵션
enabled,
networkMode,
initialData,
initialDataUpdatedAt,
meta,
notifyOnChangeProps,
placeholderData,
queryKeyHashFn,
refetchInterval,
refetchIntervalInBackground,
refetchOnMount,
refetchOnReconnect,
refetchOnWindowFocus,
retry,
retryOnMount,
retryDelay,
select,
staleTime,
structuralSharing,
subscribed,
throwOnError,
},
queryClient
);
따라서 우선 자주 쓰는 것만 알아보도록 하자!
const { data, isLoading, error } = useQuery(queryKey, queryFn, ...option);
-
- queryKey
queryKey: unknown[]
- 쿼리 캐싱을 관리하도록 도와주는 역할을 한다. 이 값이 변경되면 쿼리가 자동으로 업데이트 한다! 즉 값이 변경되지 않으면 업데이트를 안한다.
- queryKey
-
- queryFn
(context: QueryFunctionContext) => Promise<TData>
- 쿼리에서 데이터를 요청할 때 사용하는 함수. axios에서 api로 정보를 받아오는 함수를 보통 쓴다.
그 다음은 return 값들이다.
- status: 만약 데이터를 받아 오지 않았거나 받아오는 와중의 상태를 나타낸다. Pending, error, success 등이 있고 관련해서 boolean으로 표현하는 건 isPending, isSuccess, isError, isLoadingError등 다양하게 있다!
- data: 받아온 데이터. 기본은 undefined이다.
2. useQueries
자 복수형이다. 그렇다는 말은? 여러 번 실행한다는 말이다.
따라서 useQueries는 useQuery의 훅과 동일한 쿼리 옵션 객체가 있는 배열 형태를 갖추고 있다.
const ids = [1, 2, 3];
const results = useQueries({
queries: ids.map((id) => ({
queryKey: ["post", id],
queryFn: () => fetchPost(id),
staleTime: Infinity,
})),
});
3. useInfiniteQuery
말그대로 무한 쿼리. 훅은 다 비슷하지만…
pageParam과 getNextPageParma, getPreviousPageParam이 추가 됐다.
const {
fetchNextPage,
fetchPreviousPage,
hasNextPage,
hasPreviousPage,
isFetchingNextPage,
isFetchingPreviousPage,
promise,
...result
} = useInfiniteQuery({
queryKey,
queryFn: ({ pageParam }) => fetchPage(pageParam),
initialPageParam: 1,
...options,
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
lastPage.nextCursor,
getPreviousPageParam: (firstPage, allPages, firstPageParam, allPageParams) =>
firstPage.prevCursor,
});
- initalPageParam: 기본으로 설정할 페이지 파라미터. 즉 페이지의 파라미터를 몇부터 시작할 것인가를 설정할 수 있다.
- getNextPageParam: 다음 페이지를 뭘 가져올 지 결정해주는 함수!. 옵션이다. 매개변수는 lastPage, allPage를 받는다. lastPage는 마지막으로 가져온 페이지의 데이터, allPages는 이전에 가져온 모든 페이지(누적)가 포함된 배열이다.
활용 방법은 매개변수인 allpages를 활용해서 다음 페이지를 뭐로 할 지 가져오고 마지막 페이지라면 실행시키지 않게 끔 한다.
```tsx
getNextPageParam: (lastPage, pages): number | false => {
const nextPage = pages.length + 1;
return lastPage.length === 0 ? false : nextPage;
},
```
- getPreviousPageParam: NextPage와 반대로 이전 페이지로 갈때 어떻게 할지 정해주는 옵션이라고 생각하면 된다.
Return
- data.pages:
TData[]
모든 페이지를 포함한 배열 - data.pagesParams: unknown[], 페이지의 파라미터를 가진 배
- hasNextPage: 다음 페이지가 있는지 여부를 나타내는 boolean 값이다.
- fetchNextPage는 다음 페이지의 데이터를 가져올 때 사용한다. 이전 호출 해결 여부에 상관없이 매번 qeuryFn을 호출한다. 여기서 cancelRefetch를 설정하며 해결한 뒤에 불러오도록 한다. 기본은 true!
Intersection Observer
Intersection observer는 타겟 요소와 상위 요소 또는 최상위 document 의 viewport 사이의 intersection 내의 변화를 비동기적으로 관찰하는 방법이다. 즉 타켓 대상이 보여지는 화면에 있는지 여부를 확인! 이걸 무한 스크롤과 접목 시키면, 된다 ㅎㅎ
-
ref 생성 (타켓 요소 지정): 무한 스크롤의 트리거가 될 요소를 가리키게 한다. 즉 스크롤이 이 요소에 도달하면 이벤트가 발생하도록
const ref = useRef<HTMLDivElement>(null);
-
intersectinObserver 콜백함수 생성: 이 observer가 감지하는 이벤트가 발생했다면 실행되게한다. entry.isIntersecting은 요소가 화면에 보이는지 체크 hasNextPage는 다음 페이지가 있을때만 fetchNextPage를 실행하도록 하기 useCallback 사용해서 불필요하게 재생하지 않도록 방지.
const handleIntersect: IntersectionObserverCallback = useCallback( ([entry]: IntersectionObserverEntry[], observer: IntersectionObserver) => { if (entry?.isIntersecting && hasNextPage) { fetchNextPage(); } }, [fetchNextPage, hasNextPage] ); ```
-
intersection Observer 설정하기. useEffect로 컴포넌트가 마운트되면 IntersectionObserver를 설정한다.
observer.observe(ref.current)
→ref
가 가리키는 요소를 감시하기 시작하고 threshold: 0.6로 요소가 60퍼 이상 보일때 감지하도록 설정한다. return () ⇒observer.disconnect(); 로 컴포넌트가 언마운트되면 감시를 끝내도록 한다!useEffect(() => { let observer: IntersectionObserver; if (ref.current) { observer = new IntersectionObserver(handleIntersect, { threshold: 0.6 }); observer.observe(ref.current); } return () => observer && observer.disconnect(); }, [ref, handleIntersect]); ```
-
ref 반환하기
function useInfiniteScroll({
hasNextPage,
fetchNextPage,
}: IntersectionObserverProps) {
const ref = useRef<HTMLDivElement>(null);
const handleIntersect: IntersectionObserverCallback = useCallback(
([entry]: IntersectionObserverEntry[], observer: IntersectionObserver) => {
if (entry?.isIntersecting && hasNextPage) {
fetchNextPage();
}
},
[fetchNextPage, hasNextPage]
);
useEffect(() => {
let observer: IntersectionObserver;
if (ref.current) {
observer = new IntersectionObserver(handleIntersect, { threshold: 0.6 });
observer.observe(ref.current);
}
return () => observer && observer.disconnect();
}, [ref, handleIntersect]);
return ref;
}