본문 바로가기

Programming/Next

Next.js(typescript) 무한 스크롤 구현 + RestAPI

 

1. Next.js 프로젝트 생성

npx create-next-app@latest infinite-scroll --typescript
cd infinite-scroll
npm install

 

2. 무한 스크롤 컴포넌트 작성

components/InfiniteScroll.tsx 파일 생성, 외부 API를 호출하도록 수정

// components/InfiniteScroll.tsx

import { useState, useEffect, useRef, useCallback } from 'react';

// 외부 API에서 데이터를 가져오는 비동기 함수
const fetchMoreData = async (page: number, limit: number) => {
// https://jsonplaceholder.typicode.com라는 test용 restAPI를 통해 dummy json data를 보여줌
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=${limit}`);
  const newData = await res.json();
  return newData;
};

const InfiniteScroll = () => {
  const [data, setData] = useState<any[]>([]); // 데이터를 저장하는 상태
  const [page, setPage] = useState(1); // 현재 페이지 번호를 저장하는 상태
  const [loading, setLoading] = useState(false); // 로딩 상태를 저장하는 상태
  const observerRef = useRef<IntersectionObserver | null>(null); // Intersection Observer 참조를 저장하는 ref
  const loadMoreRef = useRef<HTMLDivElement | null>(null); // 로드 모어 div 참조를 저장하는 ref

  // 초기 데이터를 로드하는 useEffect
  useEffect(() => {
    const loadInitialData = async () => {
      setLoading(true);
      const initialData = await fetchMoreData(1, 10);
      setData(initialData);
      setLoading(false);
    };

    loadInitialData();
  }, []);

  // 더 많은 데이터를 로드하는 함수
  const loadMore = async () => {
    setLoading(true);
    const newPage = page + 1;
    const newData = await fetchMoreData(newPage, 10);
    setData((prevData) => [...prevData, ...newData]);
    setPage(newPage);
    setLoading(false);
  };

  // Intersection Observer의 콜백 함수
  const handleObserver = useCallback(
    (entries: IntersectionObserverEntry[]) => {
      const target = entries[0];
      if (target.isIntersecting) {
        loadMore();
      }
    },
    [page]
  );

  // Intersection Observer를 설정하는 useEffect
  useEffect(() => {
    observerRef.current = new IntersectionObserver(handleObserver, {
      rootMargin: '20px',
    });

    if (loadMoreRef.current) {
      observerRef.current.observe(loadMoreRef.current);
    }

    return () => {
      if (loadMoreRef.current) {
        observerRef.current?.unobserve(loadMoreRef.current);
      }
    };
  }, [handleObserver]);

  return (
    <div>
      <ul>
        {data.map((item) => (
          <li key={item.id}>{item.title}</li>
        ))}
      </ul>
      <div ref={loadMoreRef} style={{ height: '20px', backgroundColor: 'transparent' }} />
      {loading && <p>Loading...</p>}
    </div>
  );
};

export default InfiniteScroll;

코드 설명

  • fetchMoreData: JSONPlaceholder API를 호출하여 데이터를 가져오는 비동기 함수입니다. _page와 _limit 쿼리 파라미터를 사용하여 페이지네이션을 처리합니다.
  • InfiniteScroll 컴포넌트는 이전과 동일한 방식으로 동작하지만, 데이터를 외부 API에서 가져옵니다.

4. 페이지에서 컴포넌트 사용

pages/index.tsx 에서 InfiniteScroll 컴포넌트를 사용

// pages/index.tsx

import InfiniteScroll from '../components/InfiniteScroll';

const Home = () => {
  return (
    <div>
      <h1>무한 스크롤 예제</h1>
      <InfiniteScroll />
    </div>
  );
};

export default Home;

프로젝트 실행

프로젝트를 실행하여 결과 확인

npm run dev

Github URL