본문 바로가기
dev/code review

[React] 무한 스크롤 기능 구현

by masankong 2023. 6. 4.

무한스크롤은 특정 페이지의 하단에 닿았을때 API가 호출되며 콘텐츠가 끊기지 않고 계속 로드되는 사용자 경험 방식이다.

 

🤔 페이지네이션도 있는데 왜 무한스크롤인가?

2가지의 이유가 있는데 첫번째는 허들을 줄이기 위해서이다. 사용자가 클릭을 하는 하나의 액션도 허들이라고 보는데 동작하는것을 줄임으로써 액션을 하나 줄일수 있다는 생각을 했다.

두번째 이유는 사용자가 상품을 더 많이 접하게 하기 위해서이다. 사용자가 제일 처음 접하는 메인페이지에서 더 많은 양의 정보를 사용자에게 보여주어야 구매를 할 수 있는 확률이 올라간다고 생각했다. 클릭후 다시 상단으로 올라가 렌더링 하는 시간 보다는 계속적으로 데이터를 불러오는 것이 사용자에게 더 많은 양의 정보를 줄수 있을거라 생각했다.

 

 나중에 볼지도 모르니 정리해 놓는 무한스크롤 라이브러리

https://github.com/thebuilder/react-intersection-observer#readme

 

GitHub - thebuilder/react-intersection-observer: React implementation of the Intersection Observer API to tell you when an eleme

React implementation of the Intersection Observer API to tell you when an element enters or leaves the viewport. - GitHub - thebuilder/react-intersection-observer: React implementation of the Inter...

github.com

https://github.com/bvaughn/react-window#readme

 

GitHub - bvaughn/react-window: React components for efficiently rendering large lists and tabular data

React components for efficiently rendering large lists and tabular data - GitHub - bvaughn/react-window: React components for efficiently rendering large lists and tabular data

github.com

 

현재 생각하고 있는 구현 방법은 사용자의 스크롤바 특정 타겟 view port 에 도달시 다음장의 데이터를 가져오게 진행을 해보려고한다!

 무한 스크롤 기능 명세

1. API 불러오기
  1 - 1 : 새로운 데이터가 계속 쌓이게 
  1 - 2 :  더이상 새로운 데이터가 없을 경우 데이터를 불러오지 않음
2. 스크롤 이벤트 핸들러 생성
  2 - 1 : 스크롤 이벤트 등록   

 

1. fetch url 수정 

page 라는 useState hook을 새로 생성하고 기본값을 1로 주었다. 기존에 1페이지만 불러오던 fetch url은 page의 값이 변할때 같이 변하며 데이터를 불러 올 수 있도록 수정해 주었다.

그리고 데이터를 불러왔을 경우 새로 엎어지는것이 아닌 기존 상품 배열에 뒤에 추가하는것이므로 불러오는 상품들의 초기값을 기존에 nul에서 빈배열로 수정했다.

 

또한 fetchData 함수가 불려질때마다 현재 배열에 새로운 데이터가 추가로 불러와지게 수정하고 page 에 + 1을 주었다.

 

const [page, setPage] = useState(1);
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(false);
onst [error, setError] = useState(null);

const fetchProducts = async () => {
        try {
            setLoading(true);
            setError(null);
            // 데이터 비동기 함수로 가져오기
            const response = await axios.get(`https://openmarket.weniv.co.kr/products/?page=${page}`);
            const data = response.data.results;
            
            // 가져온 데이터를 현재 배열에 추가
            setProducts(prevProducts => [...prevProducts, ...data]);
            
            // 페이지 번호 증가
            setPage(prevPage => prevPage + 1);
        } catch(e) {
            setError(e);
        }
        setLoading(false);
    }


    useEffect(() => {
        fetchProducts();
    }, []);

 

2. 스크롤 이벤트 핸들러 생성

먼저 스크롤 페이지가 하단에 도달하면 데이터를 추가로 가져오게 하기 위해 스크롤 위치와 뷰포트 높이를 계산하였다.

📍scrollTop: 현 페이지 제일 상당부터 현재 화며에 보이는 부분까지의 길이, 현재 스크롤의 위치를 알 수 있음

📍scrollHeight: 전체 페이지의 길이

📍clientHeight: 현재 화면에서 보이는 height

 

전체 페이지 높이 <= 지금까지 스크롤한 길이 + 현재 보이는 부분 해당 연산이 true라면 스크롤이 제일 하단에 붙었다고 볼 수있다. 이때 fetchProducts 함수를 실행해주면 된다.

  const scrollHandle = () => {
    const scrollHeight = document.documentElement.scrollHeight;
    const scrollTop = document.documentElement.scrollTop;
    const clientHeight = document.documentElement.clientHeight;

    if (scrollHeight <= scrollTop + clientHeight) {
      fetchProducts();
    }
    console.log(products);
  };

추가로 useEffect hook안에 스크롤 이벤트를 등록하여 scrollHandle 의 조건에 달성시 컴포넌트가 마운트 될때마다 데이터를 가져오도록 했다.

  useEffect(() => {
    fetchProducts();

    //스크롤 이벤트 핸들러 등록
    window.addEventListener("scroll", scrollHandle);

    // 컴포넌트 언마운트 시 이벤트 핸들러 제거
    return () => {
      window.addEventListener("scroll", scrollHandle);
    };
  }, []);

 

🧨 page=1 데이터만 가져오는 에러발생

스크롤이 하단에 닿을시 데이터를 불러오기는 하나 계속 같은 페이지의 데이터만 불러오는 오류가 발생했다 콘솔로 page 를 찍어보니 1에서 변하지를 않았다. 또 다른 문제는 콘솔에 product를 찍어보면 계속 초기값인 빈배열이 출력되었다.

const [page, setPage] = useState(1);
 const [products, setProducts] = useState([]);

//fetchProduct 안에 있음

//데이터를 fetch한 후 1씩 증가하게 했지만 작동하지 않음
setPage((prevPage) => prevPage + 1);
setProducts((prevProducts) => [...prevProducts, ...data]);

데이터를 불러오면서 계속 초기값이 설정되는거같아 초기 데이터를 한꺼번에 불러오는 것과 스크롤시 불러오는 것을 나눠주었다

그리고 hasNext라는 useState 훅을 생성하여 상품의 개수가 80개 이하라면 false를 주어 더이상 데이터를 가져오지 않게 하였다.

 

  const [page, setPage] = useState(1);
  const [products, setProducts] = useState([]);
  const [hasNext, setHasNext] = useState(true);
  
  
  //초기 데이터
  const fetchProducts = async () => {
    try {
      setLoading(true);
      setError(null);
      const response = await axios.get(
        `https://openmarket.weniv.co.kr/products/?page=${page}`
      );
      console.log(response.data);
      return response.data;
    } catch (e) {
      setError(e);
    }
    setLoading(false);
  };
  
  // hasNext가 true일 경우에만 데이터 가져오기
  useEffect(() => {
    if (hasNext) {
      (async () => {
        await fetchProducts().then((res) => {
          setProducts((prevProducts) => [...prevProducts, ...res.results]);
          setHasNext(page + 1 <= Math.ceil(res.count / 15));
        });
      })();
    }
  }, [page, hasNext]);
  
  // scroll 이벤트 핸들러
  const scrollHandle = () => {
    const scrollHeight = document.documentElement.scrollHeight;
    const scrollTop = document.documentElement.scrollTop;
    const clientHeight = document.documentElement.clientHeight;

	// 스크롤이 하단에 위치하고 다음페이지가 있을경우에만 페이지수 증가
    if (scrollHeight <= scrollTop + clientHeight && hasNext) {
      setPage((prevPage) => prevPage + 1);
    }
  };
  
  
  useEffect(() => {
    window.addEventListener("scroll", scrollHandle);

    return () => window.removeEventListener("scroll", scrollHandle);
  }, [hasNext]);

 

어찌저찌 무한스크롤을 완료하긴 했으나 코드도 많이 복잡하고 페이지가 계속 플로스 되는 오류등이 있어 나중에 리펙토링의 필요성을 느꼈다🥹

'dev > code review' 카테고리의 다른 글

[React] Carousel 기능 구현  (0) 2023.05.31
[HTML, CSS] portfolio 페이지 만들기  (0) 2023.03.26
[HTML, CSS] login modal 만들기  (0) 2023.03.25

댓글