Next.js 및 TanStack 쿼리를 사용하여 무한 스크롤 및 페이지 매김을 구현하는 방법

개발할 대부분의 앱은 데이터를 관리합니다. 프로그램이 계속해서 확장됨에 따라 그 양도 점점 더 많아질 수 있습니다. 애플리케이션이 대량의 데이터를 효과적으로 관리하지 못하면 성능이 저하됩니다.

페이지 매김과 무한 스크롤은 앱 성능을 최적화하는 데 사용할 수 있는 두 가지 인기 있는 기술입니다. 이를 통해 데이터 렌더링을 보다 효율적으로 처리하고 전반적인 사용자 경험을 향상시킬 수 있습니다.

TanStack 쿼리를 사용한 페이지 매김 및 무한 스크롤

탄스택 쿼리—React Query의 변형 —은 JavaScript 애플리케이션을 위한 강력한 상태 관리 라이브러리입니다. 캐싱과 같은 데이터 관련 작업을 포함하여 다른 기능 중에서 애플리케이션 상태를 관리하기 위한 효율적인 솔루션을 제공합니다.

페이지 매김에는 대규모 데이터 세트를 더 작은 페이지로 나누는 작업이 포함되며, 이를 통해 사용자는 탐색 버튼을 사용하여 관리 가능한 덩어리로 콘텐츠를 탐색할 수 있습니다. 이와 대조적으로 무한 스크롤은 더욱 역동적인 탐색 경험을 제공합니다. 사용자가 스크롤하면 새로운 데이터가 자동으로 로드 및 표시되므로 명시적인 탐색이 필요하지 않습니다.

페이지네이션과 무한 스크롤링은 대용량 데이터를 효율적으로 관리하고 표현하는 것을 목표로 합니다. 둘 중 하나를 선택하는 것은 애플리케이션의 데이터 요구 사항에 따라 달라집니다.

이 프로젝트의 코드는 여기에서 찾을 수 있습니다. GitHub 저장소.

Next.js 프로젝트 설정

시작하려면 Next.js 프로젝트를 생성하세요. App 디렉터리를 사용하는 최신 버전의 Next.js 13을 설치하세요.

 npx create-next-app@latest next-project --app 

다음으로 노드 패키지 관리자인 npm을 사용하여 프로젝트에 TanStack 패키지를 설치합니다.

 npm i @tanstack/react-query 

Next.js 애플리케이션에 TanStack 쿼리 통합

Next.js 프로젝트에 TanStack Query를 통합하려면 애플리케이션의 루트(layout.js 파일)에서 TanStack Query의 새 인스턴스를 생성하고 초기화해야 합니다. 그렇게 하려면 TanStack 쿼리에서 QueryClient 및 QueryClientProvider를 가져옵니다. 그런 다음, 다음과 같이 QueryClientProvider를 사용하여 하위 소품을 래핑합니다.

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

const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};

export default function RootLayout({ children }) {
  const queryClient = new QueryClient();

  return (
    <html lang="en">
      <body>
        <QueryClientProvider client={queryClient}>
          {children}
        </QueryClientProvider>
      </body>
    </html>
  );
}

export { metadata };

이 설정은 TanStack Query가 애플리케이션 상태에 대한 완전한 액세스 권한을 갖도록 보장합니다.

useQuery 후크는 데이터 가져오기 및 관리를 간소화합니다. 페이지 번호와 같은 페이지 매김 매개변수를 제공하면 데이터의 특정 하위 집합을 쉽게 검색할 수 있습니다.

또한 후크는 캐시 옵션 설정, 로드 상태 효율적 처리 등 데이터 가져오기 기능을 사용자 정의할 수 있는 다양한 옵션과 구성을 제공합니다. 이러한 기능을 사용하면 원활한 페이지 매김 환경을 쉽게 만들 수 있습니다.

이제 Next.js 앱에서 페이지 매김을 구현하려면 src/app 디렉터리에 Pagination/page.js 파일을 만듭니다. 이 파일 내에서 다음 가져오기를 수행합니다.

 "use client"
import React, { useState } from 'react';
import { useQuery} from '@tanstack/react-query';
import './page.styles.css';

그런 다음 React 기능 구성 요소를 정의합니다. 이 구성 요소 내에서는 외부 API에서 데이터를 가져오는 함수를 정의해야 합니다. 이 경우 JSON자리 표시자 API 일련의 게시물을 가져옵니다.

 export default function Pagination() {
  const [page, setPage] = useState(1);

  const fetchPosts = async () => {
    try {
      const response = await fetch(`https://jsonplaceholder.typicode.com/posts?
                                  _page=${page}&_limit=10`);

      if (!response.ok) {
        throw new Error('Failed to fetch posts');
      }

      const data = await response.json();
      return data;
    } catch (error) {
      console.error(error);
      throw error;
    }
  };

  
}

이제 useQuery 후크를 정의하고 다음 매개변수를 객체로 지정합니다.

   const { isLoading, isError, error, data } = useQuery({
    keepPreviousData: true,
    queryKey: ['posts', page],
    queryFn: fetchPosts,
  });

keepPreviousData 값은 true입니다. 이는 새 데이터를 가져오는 동안 앱이 이전 데이터를 보존하도록 보장합니다. queryKey 매개변수는 쿼리 키(이 경우 데이터를 가져오려는 엔드포인트 및 현재 페이지)가 포함된 배열입니다. 마지막으로 queryFn 매개변수인 fetchPosts는 데이터를 가져오는 함수 호출을 트리거합니다.

앞서 언급했듯이 후크는 배열과 객체를 구조 해제하고 이를 활용하여 데이터를 가져오는 프로세스 중에 사용자 경험(적절한 UI 렌더링)을 개선하는 방법과 유사하게 압축을 풀 수 있는 여러 상태를 제공합니다. 이러한 상태에는 isLoading, isError 등이 포함됩니다.

그렇게 하려면 진행 중인 프로세스의 현재 상태에 따라 다양한 메시지 화면을 렌더링하는 다음 코드를 포함하십시오.

   if (isLoading) {
    return (<h2>Loading...</h2>);
  }

  if (isError) {
    return (<h2 className="error-message">{error.message}</h2>);
  }

마지막으로 브라우저 페이지에 렌더링할 JSX 요소에 대한 코드를 포함합니다. 이 코드는 두 가지 다른 기능도 제공합니다.

  • 앱이 API에서 게시물을 가져오면 해당 게시물은 useQuery 후크에서 제공하는 데이터 변수에 저장됩니다. 이 변수는 애플리케이션의 상태를 관리하는 데 도움이 됩니다. 그런 다음 이 변수에 저장된 게시물 목록을 매핑하고 브라우저에서 렌더링할 수 있습니다.
  • 사용자가 그에 따라 페이지가 매겨진 추가 데이터를 쿼리하고 표시할 수 있도록 두 개의 탐색 버튼(이전 및 다음)을 추가합니다.
   return (
    <div>
      <h2 className="header">Next.js Pagination</h2>
      {data && (
        <div className="card">
          <ul className="post-list">
            {data.map((post) => (
                <li key={post.id} className="post-item">{post.title}</li>
            ))}
          </ul>
        </div>
      )}
      <div className="btn-container">
        <button
          onClick={() => setPage(prevState => Math.max(prevState - 1, 0))}
          disabled={page === 1}
          className="prev-button"
        >Prev Page</button>

        <button
          onClick={() => setPage(prevState => prevState + 1)}
          className="next-button"
        >Next Page</button>
      </div>
    </div>
  );

마지막으로 개발 서버를 시작합니다.

 npm run dev 

그런 다음 브라우저에서 http://localhost:3000/Pagination으로 이동하세요.

앱 디렉터리에 Pagination 폴더를 포함시켰으므로 Next.js는 이를 경로로 처리하여 해당 URL의 페이지에 액세스할 수 있도록 합니다.

무한 스크롤은 원활한 탐색 경험을 제공합니다. 좋은 예는 자동으로 새 동영상을 가져와 아래로 스크롤할 때 표시하는 YouTube입니다.

useInfiniteQuery 후크를 사용하면 서버에서 페이지 단위로 데이터를 가져오고 사용자가 아래로 스크롤할 때 자동으로 다음 데이터 페이지를 가져와 렌더링하여 무한 스크롤을 구현할 수 있습니다.

무한 스크롤을 구현하려면 src/app 디렉터리에 InfiniteScroll/page.js 파일을 추가하세요. 그런 다음 다음 가져오기를 수행합니다.

 "use client"
import React, { useRef, useEffect, useState } from 'react';
import { useInfiniteQuery } from '@tanstack/react-query';
import './page.styles.css';

다음으로 React 기능 구성 요소를 만듭니다. 페이지 매김 구현과 유사하게 이 구성 요소 내에서 게시물의 데이터를 가져오는 함수를 만듭니다.

 export default function InfiniteScroll() {
  const listRef = useRef(null);
  const [isLoadingMore, setIsLoadingMore] = useState(false);

  const fetchPosts = async ({ pageParam = 1 }) => {
    try {
      const response = await fetch(`https://jsonplaceholder.typicode.com/posts?
                                  _page=${pageParam}&_limit=5`);

      if (!response.ok) {
        throw new Error('Failed to fetch posts');
      }

      const data = await response.json();
      await new Promise((resolve) => setTimeout(resolve, 2000));
      return data;
    } catch (error) {
      console.error(error);
      throw error;
    }
  };

  
}

페이지 매김 구현과 달리 이 코드는 데이터를 가져올 때 2초의 지연을 도입하여 사용자가 스크롤하여 새로운 데이터 세트의 다시 가져오기를 트리거하는 동안 현재 데이터를 탐색할 수 있도록 합니다.

이제 useInfiniteQuery 후크를 정의하세요. 구성 요소가 처음 마운트되면 후크는 서버에서 첫 번째 데이터 페이지를 가져옵니다. 사용자가 아래로 스크롤하면 후크가 자동으로 다음 데이터 페이지를 가져와서 구성 요소에 렌더링합니다.

   const { data, fetchNextPage, hasNextPage, isFetching } = useInfiniteQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts,
    getNextPageParam: (lastPage, allPages) => {
      if (lastPage.length < 5) {
        return undefined;
      }
      return allPages.length + 1;
    },
  });

  const posts = data ? data.pages.flatMap((page) => page) : [];

Posts 변수는 여러 페이지의 모든 게시물을 단일 배열로 결합하여 데이터 변수의 평면화된 버전을 만듭니다. 이를 통해 개별 게시물을 쉽게 매핑하고 렌더링할 수 있습니다.

사용자 스크롤을 추적하고 사용자가 목록 하단에 가까울 때 더 많은 데이터를 로드하려면 Intersection Observer API를 활용하여 요소가 뷰포트와 교차하는 시기를 감지하는 함수를 정의할 수 있습니다.

   const handleIntersection = (entries) => {
    if (entries[0].isIntersecting && hasNextPage && !isFetching && !isLoadingMore) {
      setIsLoadingMore(true);
      fetchNextPage();
    }
  };

  useEffect(() => {
    const observer = new IntersectionObserver(handleIntersection, { threshold: 0.1 });

    if (listRef.current) {
      observer.observe(listRef.current);
    }

    return () => {
      if (listRef.current) {
        observer.unobserve(listRef.current);
      }
    };
  }, [listRef, handleIntersection]);

  useEffect(() => {
    if (!isFetching) {
      setIsLoadingMore(false);
    }
  }, [isFetching]);

마지막으로 브라우저에서 렌더링되는 게시물에 대한 JSX 요소를 포함합니다.

   return (
    <div>
      <h2 className="header">Infinite Scroll</h2>
      <ul ref={listRef} className="post-list">
        {posts.map((post) => (
          <li key={post.id} className="post-item">
            {post.title}
          </li>
        ))}
      </ul>
      <div className="loading-indicator">
        {isFetching ? 'Fetching...' : isLoadingMore ? 'Loading more...' : null}
      </div>
    </div>
  );

모든 변경을 완료한 후 http://localhost:3000/InfiniteScroll을 방문하여 실제 적용되는 내용을 확인하세요.

TanStack 쿼리: 단순한 데이터 가져오기 그 이상

페이지 매김 및 무한 스크롤은 TanStack 쿼리의 기능을 강조하는 좋은 예입니다. 간단히 말해서, 이는 만능 데이터 관리 라이브러리입니다.

광범위한 기능 세트를 사용하면 효율적인 상태 처리를 포함하여 앱의 데이터 관리 프로세스를 간소화할 수 있습니다. 다른 데이터 관련 작업과 함께 웹 애플리케이션의 전반적인 성능은 물론 사용자 경험도 향상시킬 수 있습니다.