무한 스크롤

J·2023년 8월 1일
0

todays-recipe

목록 보기
4/9
post-thumbnail

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

왜 무한 스크롤?

  1. ux 고려: 사용자가 볼 각 아이템은 이미지가 포함되어 있음. 이미지의 크기를 고려한다면 해당 페이지에 페이지네이션 방식은 적합하지 않다고 생각했음. 사용자가 페이지를 전환해 소량의 아이템을 한 페이지 씩 확인하기보다 필요에 따라 스크롤을 내리면 같은 화면에서 끊임 없는 아이템을 확인할 수 있도록 만들고 싶었음.
  2. footer 無: 페이지에 footer 영역이 존재하지 않아 사용자가 footer 를 통해 확인할 정보가 없음.

코드


...

  // 초기 표시할 페이지
  const [currentPage, setCurrentPage] = useState(1);

  // 페이지 당 표시할 아이템 수
  const itemsPerPage = 8;

  // 함수 호출 시 현재 페이지 +1
  const loadMorePage = () => {
    setCurrentPage((prev) => prev + 1);
  };

  // 스크롤 이벤트 발생 시, 스크롤 위치 확인. 스크롤이 맨 아래 위치하면 loadMorePage 함수 호출해 페이지 로드
  const handleScroll = () => {
    const scrollTop =
      document.documentElement.scrollTop || document.body.scrollTop;
    const scrollHeight =
      document.documentElement.scrollHeight || document.body.scrollHeight;

    if (scrollHeight - scrollTop === window.innerHeight) {
      loadMorePage();
    }
  };

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);

  // 기존 sortedRecipes(filteredRecipes)에서 slice 메서드를 통해 현재 페이지에 해당하는 레시피를 보여줌
  const showRecipes = sortedRecipes(filteredRecipes).slice(
    0,
    currentPage * itemsPerPage
  );

...

  return (
    <>
		...
        <Recipes>
          {showRecipes.map((recipe: Recipe) => (
            <RecipeCard recipe={recipe} key={recipe.id} />
          ))}
        </Recipes>
		...
    </>
  );
  • 라이브러리를 사용하지 않고 구현해보고 싶었음.
    1. currentPage, setCurrentPage: 현재 페이지를 저장하는 state 생성.
    2. itemsPerPage: 각 페이지마다 8개의 아이템을 표시하도록 설정.
    3. loadMorePage: 현재 페이지를 1증가 시키도록 설정.
    4. handScroll: 스크롤 위치를 확인해, 최하단에 도달할 경우 loadMorePage 함수 호출.
    5. useEffect를 사용해 컴포넌트 마운트 시 scroll 이벤트 리스너 등록, 언마운트 시 제거.
    6. showRecipes: 필터링된 레시피 중 현재 페이지에 해당하는 레시피만 선택적으로 보여주기 위해 start index는 0, end index는 currentPage * itemsPerPage로 설정.

커스텀 훅으로 변환 & 보완: 상세 페이지 열람 후 브라우저에서 뒤로 가기를 통해 페이지 전환 시 다시 같은 스크롤에 위치하도록 초기 페이지를 세션에서 가져오도록 코드 추가함.

// useInfinityScroll.ts

import { useState, useEffect } from 'react';

// 무한스크롤 사용하는 페이지에 적용할 훅

const useInfiniteScroll = () => {
  // 로딩 상태
  const [isLoading, setIsLoading] = useState<boolean>(false);

  // 초기 페이지를 세션에서 가져옴. 없으면 1로 설정.
  const initialPage = () => {
    const savedPage = sessionStorage.getItem('last_page');
    return savedPage ? Number(savedPage) : 1;
  };

  // 현재 페이지를 저장하는 state. 초기값은 initial page
  const [currentPage, setCurrentPage] = useState(initialPage);

  // 함수 호출 시 현재 페이지 +1. 세션에 저장.
  const loadMorePage = () => {
    // 로딩 시작 전에 로딩 상태를 'false'로 변경
    setIsLoading(false);
    setCurrentPage((prev) => {
      sessionStorage.setItem('last_page', (prev + 1).toString());
      return prev + 1;
    });
    setIsLoading(true);
  };

  // 스크롤 이벤트 발생 시, 스크롤 위치 확인. 스크롤이 맨 아래 위치하면 loadMorePage 함수 호출해 페이지 로드
  const handleScroll = () => {
    const scrollTop =
      document.documentElement.scrollTop || document.body.scrollTop;
    const scrollHeight =
      document.documentElement.scrollHeight || document.body.scrollHeight;

    if (scrollHeight - scrollTop === window.innerHeight) {
      loadMorePage();
    }
  };

  // 컴포넌트 마운트될 때 이벤트 리스너 등록, 언마운트 시 제거
  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);

  return { currentPage, isLoading };
};

export default useInfiniteScroll;
// 훅을 사용하는 페이지
...
  // infinity scroll hook
  const { currentPage } = useInfiniteScroll();
  const showRecipes = sortedRecipes(filteredRecipes).slice(0, currentPage * 8);
..
  • 해당 방법으로 무한 스크롤을 구현하면 스크롤 이벤트가 발생할 때마다(페이지가 화면의 최하단에 닿을 때) 리렌더링이 일어나 성능 개선이 필요함.
profile
벨로그로 이사 중

0개의 댓글