[Project] 무한 스크롤 구현하기

✨ 강은비·2022년 6월 12일
0

Project

목록 보기
3/9
post-thumbnail

페이지네이션 🆚 무한스크롤

🐽 모아모아에 커뮤니티 최근 게시물 리스트 보기 기능이 있다. 이렇게 리스트 형태로 나열되는 콘텐츠를 보여주는 방법 중 대표적으로 페이지네이션과 무한 스크롤이 있다.

📄 페이지네이션

페이지네이션은 콘텐츠를 여러 페이지로 나눠서 보여주는 방법이다. 페이지 하단에는 페이지를 이동할 수 있는 내비게이션이 있다.

장점

  • 사용자 의도에 맞게 페이지를 넘길 수 있고 그로 인해 사용자는 통제감을 느낄 수 있다.
  • 어떤 페이지에 어떤 항목이 있는지 콘텐츠의 위치를 쉽게 파악할 수 있다.

단점

  • 페이지 이동 시 콘텐츠가 로드될 때까지 기다려야 한다.
  • 한 페이지에 제한된 내용만 보여주기 때문에 원하는 정보를 얻기 위해 내비게이션의 추가 클릭이 필요하다.

♾️ 무한스크롤

스크롤이 하단에 도달했을 때, 콘텐츠가 그 아래로 계속 보여지는 방법이다. 페이지네이션과 달리 한 페이지에 콘텐츠들을 보여준다.

장점

  • 스크롤만을 통해 콘텐츠들이 계속 로드되니까 콘텐츠 탐색이 쉽다.
  • 다음 콘텐츠를 보기 위한 추가 클릭이 필요없어서 더 나은 사용자 경험을 제공한다.

단점

  • 페이지 성능이 느려진다.
  • 특정 항목이 어느 위치에 있는지 파악하기 힘들고 원래 위치로 되돌아오기 어렵다.
  • footer를 찾기가 어려워진다.

👉 무한스크롤은 사용자들이 직접 끊임없이 생성하는 콘텐츠가 많은 애플리케이션(ex. 소셜미디어)에 적합하다. 반면 페이지네이션은 사용자가 가야 할 방향이 뚜렷한, 목표지향적인 애플리케이션(ex. 검색 기능이 있는 앱)에 적합하다.

🐽 모아모아 애플리케이션에는 ♾️ 무한스크롤을 적용했다. 프론트 팀에서는 더 나은 사용자 경험을 제공하고 싶었다. 그래서 사용자의 클릭을 최소화하여 쉽고 한 번에 다양한 콘텐츠를 보여줄 수 있는 무한스크롤 방법을 선택했다.


✏️ 무한스크롤 구현하기

  • 스크롤이 바닥에 닿으면 20개의 콘텐츠를 불러온다. 이때 page 이름의 쿼리 파라미터가 필요하다.
  • 실제로 무한 스크롤은 한 페이지에 콘텐츠들을 보여주는 것이지만 api 설계할 때는 page 파라미터가 필요하다.
  • 더 이상 불러올 콘텐츠가 없으면 무한 스크롤을 멈춘다.

👉 React QueryuseInfiniteQueryIntersectionObserver API를 이용하여 무한 스크롤을 구현했다.

참고


♾️ useInfiniteQuery

프로젝트 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와 비슷하면서 다르다.

  • useQuery와 동일하게 queryKeydatafetch하는 callback 함수 (api 호출 함수)가 있어야 한다.
  • 이때 콜백함수 인자로 QueryFunctionContext라는 객체가 들어오게 된다.
  • 이 객체에는 pageParam property 있고 기본값을 설정해야 한다. 이 기본값은 처음 callback함수를 호출할 때 사용된다.
    ✏️ 위 코드에서는 백엔드가 설계한 api에 따라 pageParam 기본값을 0으로 설정했다.
  • option들 중 getNextPageParam 함수가 있다.
    • 이 함수는 앞서 설명한 pageParam의 다음 값을 반환하거나 더 이상 불러올 콘텐츠가 없으면 undefined를 반환해야 한다.
    • 인자로는 가장 마지막으로 fetchapi response data와 지금까지 fetchapi 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를 알게 되었다.


🔥 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 elementviewport가 교차하는지 즉, target element가 화면에 노출되었는지 요소의 가시성을 관찰할 수 있다.
  • 이때 target element항상 콘텐츠 리스트의 맨 마지막에 위치해놓으면 target element가 화면에 노출되는 동시에 스크롤이 바닥에 닿게 된다.

👉 이제 관찰 대상이 화면에 노출되고 hasNextPage 값이 true이면 fetchNextPage 함수를 호출하면 된다.

✏️ 코드 설명

  • hasNextPage 값이 바뀌면 useEffect의 콜백함수가 실행된다.
  • 콜백함수 내부에서 hasNextPage 값이 false 이면 return한다.
  • hasNextPage 값이 true이면 loadMoreRef가 선택한 html 요소를 Intersection Observer API로 관찰한다.
  • 관찰 대상 요소가 viewport와 교차되면 (화면에 노출되면) fetchNextPage함수를 호출한다.

0개의 댓글