DREAMCARD_무한스크롤_useInfinitiyQuery + interception Observer Refactoring

정소현·2024년 11월 1일
0

팀프로젝트

목록 보기
35/50

맨 처음 작업했던 후기 보여주는 사이트를 무한스크롤로 구현했는데 그 때 > useInfinityQuery와 Scroll event로 무한스크롤을 구현했었다.
다른 팀원분이 작업하신 캐러셀 부분애서 후기를 불러오고 페이지를 연동하였을 때 계속해서 오류가 나 오류를 해결하면서 스크롤 이벤트의 성능 저하를 함께 개선하기 위해
useInfinityQuery + interception Observer를 사용하여 리팩토링을 결정했다.
스크롤 이벤트를 계산할 때는 매번 스크롤 위치를 검사해줘야 해서 성능 이슈가 발생할 수 있다.


Intersection Observer API

Intersection Observer API는 target 요소와 root 요소 사이의 교차 발생을 비동기적으로 관찰한다.
Dom과 target이 닿을 때 Callback이 실행되어 데이터를 불러오게 된다.

자주 쓰이는 메서드

  • observe()
    : 대상 요소 (target)의 관찰을 시작할 때 사용한다.
  • unobserve()
    : 대상 요소의 관찰을 중지할 때 사용한다. 관찰을 중지할 하나의 대상 요소를 인수로 지정해야한다.
  • disconnect()
    : IntersectionObserver 인스턴스가 관찰하는 모든 요소의 관찰을 중지할 때 사용한다

Infinite Scroll (무한 스크롤)

  • Next.js 12
  • react-query
  • TypeScript

    지난번에도 사용했던 useInfinityQuery 기본 구조

    const {
     fetchNextPage,
     fetchPreviousPage,
     hasNextPage,
     hasPreviousPage,
     isFetchingNextPage,
     isFetchingPreviousPage,
     ...result
    } = useInfiniteQuery({
     queryKey,
     queryFn: ({ pageParam = 1 }) => fetchPage(pageParam),
     ...options,
     initialPageParam: 0,
     getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
     getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor,
    })

기능설명

  • fetchNextPage
    : 다음 페이지 요청
  • hasNextPage
    : 다음 페이지 있는지 확인
  • isFetchingNextPage
    : 다음 페이지 불러오는지 관찰
  • initialPageParam
    : 현재 페이지
  • getNextPageParam (option)
    lastPageallPages는 콜백함수에서 리턴된 값
    lastPage는 직전에 반환된 리턴값
    allPages는 이제까지 받아온 전체 페이지.
    마지막 페이지일 경우 undefined를 리턴하여
    hasNextPage값을 false로 설정

Custom Hook 분리하기

  • queryKey 따로 분리
export const QUERY_KEYS = {
  guestBook: (invitationId: string) => ['guestbook', invitationId] as const,
  invitation: () => ['invitation'] as const,
  stickerImages: () => ['stickerImages'],
  invitationCard: () => ['invitationCard'],
  reviewCarousel: () => ['reviewCarousel'],
  authUsers: () => ['authUsers'],
  allImageReviews: () => ['allImageReviews'],
  reviews: () => ['reviews'],
};

useInfinityQuery 커스텀 훅 만들기

  • useGetReview.ts
export const useReviewInfinite = () => {
  const row = 10;

  return useInfiniteQuery<Review[]>({
    queryKey: QUERY_KEYS.reviews(),
    queryFn: async ({ pageParam }) => await getReview({ pageParam: pageParam as number, row }),
    initialPageParam: 0,
    getNextPageParam: (lastPage, allPages) => {
      return lastPage.length === row ? allPages.length * row : undefined;
    },
  });
};


IntersectionObserver 를 사용한 커스텀 훅

  • type 지정
const ReviewPage = () => {
  const { data: reviewsData, isLoading, error, fetchNextPage, hasNextPage, isFetchingNextPage } = useReviewInfinite();
  const observerRef = useRef<IntersectionObserver | null>(null);

  const lastReviewRef = useCallback(
    (node: HTMLDivElement | null) => {
      if (isFetchingNextPage) return;

      if (observerRef.current) observerRef.current.disconnect();

      observerRef.current = new IntersectionObserver((entries) => {
        if (entries[0].isIntersecting && hasNextPage) {
          fetchNextPage();
        }
      });

      if (node) observerRef.current.observe(node);
    },
    [isFetchingNextPage, fetchNextPage, hasNextPage],
  );

  if (isLoading) return <div>로딩 중...</div>;
  if (error) return <div>에러가 발생했습니다: {error.message}. 다시 시도해 주세요.</div>;

  const reviews = reviewsData?.pages.flatMap((page) => page) || [];

  return (
    <div className='flex flex-col w-full p-4'>
      <h1>후기</h1>
      <ReviewImage />
      <div>
        <ReviewCard reviews={reviews} />
        {reviews.length > 0 && (
          <div
            ref={lastReviewRef}
            className='h-1'
          />
        )}
      </div>
      {isFetchingNextPage && <div>더 불러오는 중...</div>}
    </div>
  );
};

export default ReviewPage;

💥 Trouble Shooting

🔥 문제점
: 다른 팀원분이 작성하신 부분에서 계속 동일하게 오류가 나는 부분이 있었다. 홈화면의 후기 캐러셀 부분에서 후기 페이지로 넘어갈 때 계속해서 length 오류가 나는 것이었다. 어떤 부분에서 나는 지 오류를 해결하지 못하다가 queryKey를 분리하는 과정에서 두 컴포넌트에서 같은 queryKey를 사용하면서 오류를 발생시켰다는 것을 알게되었다.

✨ 해결방법
: 각자 다른 세분화된 queryKey를 부여한 이후 문제없이 정보를 잘 불러오게 되었다.
queryKey가 흩어져 있을 경우 관리가 쉽지않고 어디서 오류가 나는 지 찾기 쉽지 않은 것 같다. 그리고 명확한 이름과 변수명을 칭하는 것이 얼마나 중요한 지 한번 더 깨닫게 되었다.

0개의 댓글