Infinite Scroll 무한스크롤
화면을 아래로 스크롤 할 때 콘텐츠가 지속적으로 로드되어야 하는경우 페이지네이션 대신 무한스크롤이 사용된다
장단점으로는 다음과 같다
장점
1. 새로운 콘텐츠 조회의 편리함 : 다음, 더 보기와 같은 상호작용 없이 바로바로 로드가 가능
2. 모바일 기기에 적합 : PC에서도 마찬가지로 작은 뷰 구성엔 적합한 경우가 빈번
단점
1. 특정 콘텐츠 조회의 어려움 : 특정 위치의 콘텐츠를 조회 및 기억하기가 어려움
2. 페이지 속도의 감소 : 콘텐츠가 방대할 수록 로드하는데에 더 많은 시간이 소요됨
3. 푸터가 fixed가 아닌 경우 조회 지연
장단점이 명확한 만큼 내가 구현하려는 곳의 성질에 맞게 체계적이고 조건에 맞는 컨텐츠가 조회되어야하는 경우엔 페이지 네이션, 단순히 무한 스크롤에 따른 간편한 조회가 이루어져야 하는 경우엔 무한 스크롤을 사용하는 것이 좋을 것 같다
구현
구현하는 방법에 있어서는 일반 페이지네이션에 비해서는 조금 난이도가 있는편있는 것 같다
보통은 데이터 페칭과 새로운 데이터 필요여부 감지를 구분해 구현한다
1. tanstack-query의 각 프레임워크 별 useInfiniteQuery를 사용해 데이터 페칭을 진행한다
2. IntersectionObserver를 통해 스크롤 감지를 해 새로운 부분을 불러오도록 한다
Next에서 사용한 코드는 다음과 같다
query를 사용할 곳에 QueryClientProvider로 래핑을 해준다
page.tsx
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import Infinite from "./test/Infinite";
export default function Home() {
const queryClient = new QueryClient();
return (
<div className="flex flex-col items-center gap-4 p-6 min-w-md mx-auto">
<QueryClientProvider client={queryClient}>
<Infinite />
</QueryClientProvider>
</div>
);
}
Infinite.tsx : 무한스크롤 구현 컴포넌트
const fetchImages = async ({ pageParam = 1 }) => {
const res = await axios.get(`https://api.unsplash.com/photos/random`, {
params: {
client_id: UNSPLASH_ACCESS_KEY,
count: 10,
page: pageParam,
},
});
return { data: res.data, nextPage: pageParam + 1 };
};
위 코드는 데이터 페칭 부분으로, 랜덤이미지를 가져올 수 있는 UNSPLASH api를 이용해 가져오도록했다
한번에 가져오는(이미지)양은 10으로 설정하고, pageParam을 통해 다음 페이지를 불러오도록 했다
반환 값은 데이터와 다음 페이지를 나타내는 nextPage가 포함된 객체형태로 표현했다
const Infinite = () => {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } =
useInfiniteQuery({
queryKey: ["images"],
queryFn: fetchImages,
getNextPageParam: (lastPage) => lastPage.nextPage,
initialPageParam: 1,
});
const observerElem = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && hasNextPage) {
fetchNextPage();
}
},
{ threshold: 1 }
);
if (observerElem.current) {
observer.observe(observerElem.current);
}
return () => {
if (observerElem.current) {
observer.unobserve(observerElem.current);
}
};
}, [hasNextPage, fetchNextPage]);
if (status === "error") return <div>Error</div>;
return (
<div>
{data?.pages.map((page, i) => (
<React.Fragment key={i}>
{page.data.map((image: ImageDataTypes) => (
<img
key={image.id}
src={image.urls.small}
alt={image.alt_description}
style={{ width: "100%", maxWidth: "300px", margin: "10px" }}
/>
))}
</React.Fragment>
))}
<div ref={observerElem}>
{isFetchingNextPage ? <div>Loading more...</div> : <div>Load more</div>}
</div>
</div>
);
};
export default Infinite;
위 코드는 useInfiniteQuery를 사용해 본격적으로 무한 스크롤을 구현해 view까지 연결한 코드이다
✔ queryKey: ["images"] → 캐싱을 위한 키
✔ queryFn: fetchImages → 위에서 선언한 API 호출 함수
✔ getNextPageParam: (lastPage) => lastPage.nextPage → 마지막 페이지 데이터를 참고하여 다음 페이지 번호 반환
✔ initialPageParam: 1 → 초기 페이지 설정 (1부터 시작)
위 코드중 useEffect 부터는 스크롤 감지 부분으로 끝부분에 도달하면 새로운 데이터를 요청한다
✔ useRef()로 observerElem을 지정 (DOM 참조)
✔ useEffect() 내부에서 IntersectionObserver 생성
✔ entries[0].isIntersecting → 감지 대상이 화면에 보이면 실행
✔ fetchNextPage() 호출하여 다음 페이지 데이터 요청
✔ unobserve(observerElem.current) → 컴포넌트가 사라질 때 옵저버 해제
초기 데이터를 요청 -> 스크롤 감지를 통해 끝 부분 도달시 새로운 데이터 요청후 pages 배열에 합치기 이 과정이 반복되는 과정이다
개선점
여기에 추가로 추가 요청 중 비동기로 스켈레톤 UI를 보여줘 UX를 향상 시키는 방법이나 지금은 단순히 error text로 보여주는 부분을 좀더 시각적으로 좋게 바꾸는 방법도 있다
또한 위에서 말한 무한 스크롤의 문제점을 극복하기 위해 기본적으로 보여지는 데이터를 설정하고 (Ex. 10개)
더보기 버튼과 같은 요소를 통해 무한스크롤을 사용자에게 하여금 원할 때만 작동하도록 선택하는 방법도 있다
'코딩 > REACT' 카테고리의 다른 글
클래스형 컴포넌트 (0) | 2025.04.01 |
---|---|
kyuwitter 클론코딩 (2) .env생성 (0) | 2023.02.26 |
kyuwitter 클론코딩 (1) firebase setup (0) | 2023.02.26 |
React 부록 async, await, arrow function(2편) (2) | 2022.06.24 |
React 부록 async, await, arrow function(1편) (3) | 2022.06.23 |