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

페이지네이션무한스크롤 중 어떤 쪽을 사용할지는 웹사이트의 목적, 콘텐츠의 유형 그리고 의도된 UX에 따라 달라진다. 페이지네이션은 사용자가 특정한 콘텐츠를 찾고 있는 웹사이트에 가장 적합하다. 무한 스크롤은 사용자가 무언가 흥미로운 콘텐츠를 보기 위해 목적 없이 검색하는 상황에서 더 적절하며, 모바일 기기에도 효과적이다.

정리하면

페이지네이션은 디지털 콘텐츠를 웹사이트의 또 다른 페이지들로 분리하는 방법이다. 사용자는 페이지에 있는 숫자 형식의 링크를 클릭해 페이지들을 탐색할 수 있다.

무한 스크롤은 사용자가 페이지 하단에 도달했을 때, 콘텐츠가 계속 로드되는 사용자 경험(UX, User EXperience) 방식이다. 끝이 없는 단일 페이지에서 끝없는 정보의 흐름을 경험하게 만들 수 있다.

무한스크롤 구현 방법

scroll event를 감지하는 방법

무한스크롤은 스크롤리 페이지 끝에 닿은 것을 감지하고 추가로 데이터를 받아오는 방식이다.

페이지 끝에 도달한 식을 표현
**전체 페이지 높이** ≤ **지금까지 스크롤한 길이** + **현재 보이는 부분**

Untitled

스크롤 이벤트가 발생할 때마다 이벤트를 발생시키면 성능 저하로 이어질 수 있다. 이를 개선하기 위해서는 Intersection Observer API을 적용해 최적화가 가능하다.

Intersection Observer API

스크롤 이벤트로 무한 스크롤을 구현하면 리플로우에 의해 좋지 않은 렌더링 성능과 상황에 따라 기대한 대로 동작하지 않을 수 있는 문제점이 있다. 이를 해결하기 위해 Intersection Observer API를 사용한다.

Intersection Observer API는 기본적으로 브라우저 ViewportTarget으로 설정한 요소의 교차점을 관찰하여 그 Target이 Viewport에 포함되는지 구별하는 기능을 제공한다.

실제 코드

실제로 MyShop에서 상품 목록을 가져오는 코드다.

const ItemListPage = () => {
  const { type } = useParams();
  const dispatch = useDispatch();

  const {categoryItems, page, isLast, loading} = useSelector(({categoryItems, loading}) => ({
    categoryItems: categoryItems.categoryItems,
    error: categoryItems.error,
    page: categoryItems.page,
    isLast: categoryItems.isLast,
    loading: loading['categoryItems/LIST_CATEGORY_ITEM'],
  }));

  const **observerRef** = useRef(null);
  **const observerCallback = useCallback((entries) => {
    const target = entries[0];
    if (target.isIntersecting && !loading && !isLast) {
      dispatch(loadCategoryItems({type, page}));
    }
  }, [type, page, isLast, dispatch]);**

  useEffect(() => {
    if (observerRef.current) {
      const observer = new IntersectionObserver(**observerCallback**);
      **observer.observe(observerRef.current);**

      return () => {
        observer.disconnect();
      }
    }
  }, [observerRef.current, observerCallback, type, page, isLast]);

  useEffect(() => {
    dispatch(unloadCategoryItems());
  }, [type]);

  return (
    <>
      {!loading && categoryItems && (
        <>
          <div className="item-list">
            {categoryItems.map((item, index) => (
              <Item item={item} key={index}/>
            ))}
          </div>
          {!error && !loading && !isLast && (
            <div style={{width: "100%", height: "30px"}} ref={**observerRef**} />
          )}
        </>
      )}
    </>
  );
}

먼저 target 요소를 저장하기 위해 ref를 선언하고 target이 교차 상태(true)라면 observerCallback 함수를 실행하는 함수(다음 페이지를 가져옴)를 선언했다.

이후 useEffect로 IntersectionObserver 객체를 생성하고 observe 메서드를 통해 target 요소의 관찰을 시작한다. 컴포넌트가 언마운트될 때는 **observer.disconnect()**를 호출해 모든 요소의 관찰을 중지시킨다.