CS 대비/FrontEnd Part

[Next.js] Next.js 복습 (3) - React-Query

JellyApple 2023. 12. 22. 23:40

Next.js 세 번째 복습으로 react-query를 선택했다. react-query는 React에서 사용해본 경험은 있지만 Next.js에서 서버 컴포넌트와 클라이언트 컴포넌트에서 사용하는 방식이 달라서 이를 기록하고자 했다.


그 전에 React-Query를 사용 하는 이유에 대해 생각해보자.

1) 서버의 데이터를 가져오는 것 / 리덕스는 컴포넌트끼리 데이터 공유 할 때 사용

2) 캐싱이 가능해서 트래픽 줄여줌

3) query.getClient로 데이터 공유도 가능

4) React-Query로 캐싱 후 zustand로 데이터 공유 자주 사용

5)  로딩 , 에러 , 펜딩 , 성공 같은 인터페이스 표준화 지원

 

 

다음으로 Next.js에서 React-Query를 사용하는 방법은 크게 두 가지가 있다고 한다. 

1. initalData 

2. 서버에서 캐시를 Dehydrate 후 클라이언트에서 Hydrate 하는 방식

 

이 강의에서는 2번째 방법을 사용했다. 

 Hydrate : 서버에서 불러온 데이터를 클라이언트에서 형식 그대로 받아오는 것

 

순서는 아래와 같다.

1. RQProvider.tsx로 리액트 쿼리 설정 만들어줌(리액트와 동일) 다만 차이는 React에서는 App.tsx나 index.tsx를 감싸줬다면 얘는 클라이언트 컴포넌트이기 때문에 따로 서버 컴포넌트와 구분을 위해 컴포넌트화 해줬다.

"use client";
import React, { useState } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

type Props = {
  children: React.ReactNode;
};

export default function RQProvider({ children }: Props) {
  const [client] = useState(
    new QueryClient({
      defaultOptions: {
        queries: {
          refetchOnWindowFocus: false,
          retry: false,
        },
      },
    })
  );
  return (
    <QueryClientProvider client={client}>
      {children}
      <ReactQueryDevtools
        initialIsOpen={process.env.NEXT_PUBLIC_NODE === "local"}
      />
    </QueryClientProvider>
  );
}

 

2. 서버에서 prefetching 해서 Dehydrate 후 클라이언트에서 hydrate 해주는 과정
: 아래 코드는 무한스크롤 구현된 것인데 queryClient.prefetching으로 바꿔주면 클라이언트 컴포넌트에서 쓰이는 useQuery와 같은 역할을 한다. 

import TabDecider from "@/app/(afterLogin)/home/_component/TabDecider";
import {
  dehydrate,
  HydrationBoundary,
  QueryClient,
} from "@tanstack/react-query";
import { getPostRecommends } from "@/app/(afterLogin)/home/_lib/getPostRecommends";

export default async function TabDeciderSuspense() {
  const queryClient = new QueryClient();
  await queryClient.prefetchInfiniteQuery({
    queryKey: ["posts", "recommends"],
    queryFn: getPostRecommends,
    initialPageParam: 0,
  });
  const dehydratedState = dehydrate(queryClient);

  return (
    <HydrationBoundary state={dehydratedState}>
      <TabDecider />
    </HydrationBoundary>
  );
}

3. 클라이언트 컴포넌트에서의 React-Query

const { data, fetchNextPage, hasNextPage, isFetching, isPending, isError } =
    useInfiniteQuery<
      IPost[],
      Object,
      InfiniteData<IPost[]>,
      [_1: string, _2: string],
      number
    >({
      queryKey: ["posts", "recommends"],
      queryFn: getPostRecommends,
      initialPageParam: 0,
      getNextPageParam: (lastPage) => lastPage.at(-1)?.postId,
      staleTime: 60 * 1000, // 1분
      gcTime: 300 * 1000,
    });
  const { ref, inView } = useInView({
    threshold: 0, // div가 보이고 몇 px 움직여야 이벤트가 실행 될 것인가?
    delay: 0.5, // 몇 초후에 이벤트를 실행 시킬 것인가?
  });

  useEffect(() => {
    if (inView) {
      !isFetching && hasNextPage && fetchNextPage();
    }
  }, [inView, isFetching, hasNextPage, fetchNextPage]);


4. 구성 요소 설명
1) queryKey : 이 키를 갖고 있을 때는 항상 queryFn을 실행해라
2)  queryClient.getQueryData로 데이터를 불러올 수도 있다. 

3) initalPageParam: 처음 페이지 
4) getNextPageParam: 다음 페이지를 보여주고 싶을 때 사용 
5) inview => useInView 라는 react-intersection-observer 라이브러리로 스크롤 위치 파악 후 

<div ref={ref} /> 을 통해 다음 페이지 위치 계산 가능

 

5. 기타 알아두면 좋을 것들
1) Props 있으면 <>가 아닌 <Fragment> , 없으면 <> 사용 가능 
2) Suspense : 로딩이 끝나기 전까지 보여주는 컴포넌트 계층