🐽 모아모아에 커뮤니티 최근 게시물 리스트 보기 기능이 있다. 이렇게 리스트 형태로 나열되는 콘텐츠를 보여주는 방법 중 대표적으로 페이지네이션과 무한 스크롤이 있다.
페이지네이션은 콘텐츠를 여러 페이지로 나눠서 보여주는 방법이다. 페이지 하단에는 페이지를 이동할 수 있는 내비게이션이 있다.
스크롤이 하단에 도달했을 때, 콘텐츠가 그 아래로 계속 보여지는 방법이다. 페이지네이션과 달리 한 페이지에 콘텐츠들을 보여준다.
👉 무한스크롤은 사용자들이 직접 끊임없이 생성하는 콘텐츠가 많은 애플리케이션(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
함수를 호출한다.