🐽 모아모아에 커뮤니티 최근 게시물 리스트 보기 기능이 있다. 이렇게 리스트 형태로 나열되는 콘텐츠를 보여주는 방법 중 대표적으로 페이지네이션과 무한 스크롤이 있다.
페이지네이션은 콘텐츠를 여러 페이지로 나눠서 보여주는 방법이다. 페이지 하단에는 페이지를 이동할 수 있는 내비게이션이 있다.
스크롤이 하단에 도달했을 때, 콘텐츠가 그 아래로 계속 보여지는 방법이다. 페이지네이션과 달리 한 페이지에 콘텐츠들을 보여준다.
👉 무한스크롤은 사용자들이 직접 끊임없이 생성하는 콘텐츠가 많은 애플리케이션(ex. 소셜미디어)에 적합하다. 반면 페이지네이션은 사용자가 가야 할 방향이 뚜렷한, 목표지향적인 애플리케이션(ex. 검색 기능이 있는 앱)에 적합하다.
🐽 모아모아 애플리케이션에는 ♾️ 무한스크롤을 적용했다. 프론트 팀에서는 더 나은 사용자 경험을 제공하고 싶었다. 그래서 사용자의 클릭을 최소화하여 쉽고 한 번에 다양한 콘텐츠를 보여줄 수 있는 무한스크롤 방법을 선택했다.
page 이름의 쿼리 파라미터가 필요하다.api 설계할 때는 page 파라미터가 필요하다.👉 React Query의 useInfiniteQuery와 IntersectionObserver API를 이용하여 무한 스크롤을 구현했다.
참고
프로젝트 tech stack으로 react query를 이용하고 있었다. react query api 중에 useInfiniteQuery가 있었고 이를 이용하여 쉽게 무한스크롤을 구현할 수 있을 것 같았다.
// 커뮤니티 최근 게시물 가져오기
const { data, hasNextPage, status, fetchNextPage } = useInfiniteQuery(
['recentPosts', category],
({ pageParam = 0 }) => getRecentPosts(category, pageParam),
{
getNextPageParam: (lastPage) => {
const { number, totalPages } = lastPage;
return number + 1 < totalPages ? number + 1 : undefined;
},
refetchOnWindowFocus: false,
},
);
react query의 useQuery와 비슷하면서 다르다.
queryKey와 data를 fetch하는 callback 함수 (api 호출 함수)가 있어야 한다.QueryFunctionContext라는 객체가 들어오게 된다.pageParam property 있고 기본값을 설정해야 한다. 이 기본값은 처음 callback함수를 호출할 때 사용된다.pageParam 기본값을 0으로 설정했다.option들 중 getNextPageParam 함수가 있다. pageParam의 다음 값을 반환하거나 더 이상 불러올 콘텐츠가 없으면 undefined를 반환해야 한다.fetch한 api response data와 지금까지 fetch한 api response data 배열 총 2개가 있다.fetch한 페이지 넘버(number)와 총 페이지 수(totalPages)를 이용하여 다음 pageParam 값을 반환한다.useQuery와 다르게 hasNextPage, fetchNextPage가 있다.getNextPageParam 함수의 반환값으로 hasNextPage 값이 결정된다. (true or false)hasNextPage의 값이 true이면 fetchNextPage 함수를 호출하면 된다.✔️ 커뮤니티 최근 게시물 get api 호출할 때
page쿼리파라미터가 필요한데 이를 자동으로 인자로 넣어 주고 기본값도 설정할 수 있다는 것이 정말 편했다. 그리고getNextParam함수를 전달해주면hasNextPage값과fetchNextPage함수를 이용하여 쉽게 무한스크롤을 구현할 수 있다.
👉 이제 스크롤이 바닥에 닿고 hasNextPage 값이 true이면 fetchNextPage 함수를 호출하면 된다.
window.addEventListener("scroll", callback)
window에 스크롤 이벤트 리스너를 추가하여 무한스크롤을 구현할 수도 있다. 콜백함수 내부에는 만약 스크롤바가 바닥에 도달했고 hasNextPage 값이 true이면 fetchNextPage 함수를 호출하여 콘텐츠를 불러오도록 한다.
그러면 스크롤을 움직일 때마다 콜백함수가 실행되어 스크롤바가 바닥에 도달했는지 계속 계산하게 된다. 그러면 성능 문제가 발생하게 된다.👎 그래서 다른 방법을 찾다가 IntersectionObserver API를 알게 되었다.
const loadMoreRef = useRef(null);
useEffect(() => {
if (!hasNextPage) {
return;
}
const observer = new IntersectionObserver((entries) =>
entries.forEach((entry) => {
if (entry.isIntersecting) {
fetchNextPage();
}
}),
);
const el = loadMoreRef && loadMoreRef.current;
if (!el) return;
observer.observe(el);
return () => {
observer.unobserve(el);
};
}, [hasNextPage, loadMoreRef.current]);
//...
return (
<section>
<PostList pages={data.pages} />
<p ref={loadMoreRef}>
Load More
</p>
</section>
);
Intersection Observer API는 타겟 요소와 상위 요소 또는 최상위 document 의 viewport 사이의 intersection 내의 변화를 비동기적으로 관찰하는 방법이다. target element와 viewport가 교차하는지 즉, target element가 화면에 노출되었는지 요소의 가시성을 관찰할 수 있다. target element를 항상 콘텐츠 리스트의 맨 마지막에 위치해놓으면 target element가 화면에 노출되는 동시에 스크롤이 바닥에 닿게 된다. 👉 이제 관찰 대상이 화면에 노출되고 hasNextPage 값이 true이면 fetchNextPage 함수를 호출하면 된다.
✏️ 코드 설명
hasNextPage 값이 바뀌면 useEffect의 콜백함수가 실행된다.hasNextPage 값이 false 이면 return한다.hasNextPage 값이 true이면 loadMoreRef가 선택한 html 요소를 Intersection Observer API로 관찰한다.viewport와 교차되면 (화면에 노출되면) fetchNextPage함수를 호출한다.