ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 10일 차 (무한 스크롤 구현)
    project/main 2023. 3. 16. 20:48

    CSS가 어느정도 끝났기 때문에 슬슬 필요한 기능구현을 하기로 했다. 그 중에서 나는 무한 스크롤을 하기로 했다.

     


    1. 무한 스크롤(Infinite Scroll)

    리스트들을 보는 방법은 여러가지가 있다. 

    • 데이터를 전부 받아와서 전부 한번에 나타내기
    • 페이지로 나누어 보기
    • 무한 스크롤

    이 중 내가 구현하려는 무한 스크롤은 데이터의 일부를 받아오고 이후 스크롤이 정해진 구간을 넘어 갔을때 다시 요청을 보내서 스크롤이 늘어나는 방식으로 리스트를 보여주는 방식을 말한다.

     

    구현하는 방식은 얼추 알고 있었다.

    • 일정 부분의 데이터를 처음받고 map으로 뿌려준다.
    • 스크롤이 content의 마지막에 도달했을때 다시 데이터 요청으로 보내고 리스트 업데이트 하기.
    import { useState, useEffect } from 'react';
    import styled from 'styled-components';
    import axios from 'axios';
    
    const Container = styled.div`
      display: flex;
      flex-wrap: wrap;
    `;
    
    const Item = styled.div`
      width: 200px;
      height: 200px;
      background-color: #eee;
      margin: 10px;
    `;
    
    function App() {
      const [items, setItems] = useState<string[]>([]);
      const [page, setPage] = useState(1);
    
      useEffect(() => {
        const fetchData = async () => {
          const response = await axios.get(`https://jsonplaceholder.typicode.com/photos?_page=${page}&_limit=10`);
          setItems(prevItems => [...prevItems, ...response.data.map((item: any) => item.url)]);
        };
        fetchData();
      }, [page]);
    
      const handleScroll = () => {
        const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
        if (scrollTop + clientHeight >= scrollHeight - 100) {
          setPage(prevPage => prevPage + 1);
        }
      };
    
      useEffect(() => {
        window.addEventListener('scroll', handleScroll);
        return () => window.removeEventListener('scroll', handleScroll);
      }, []);
    
      return (
        <Container>
          {items.map(item => (
            <Item key={item} style={{ backgroundImage: `url(${item})` }} />
          ))}
        </Container>
      );
    }
    
    export default App;

    이걸 같은 환경에서 실행시켜 보았다. 

    스크롤 내리기 전
    스크롤 내린 후

    스크롤바가 줄어들고 데이터가 계속해서 업데이트 되는 것을 볼 수 있다. 와.... 고대로 베낄수는 없으니 코드를 분석하기로 했다.

     

    2. 분석

      const [items, setItems] = useState<string[]>([]); //데이터 저장
      const [page, setPage] = useState(1); //현재 페이지
      
      useEffect(() => {//page가 바뀔때마다 데이터 불러오기
        const fetchData = async () => {
          const response = await axios.get(`https://jsonplaceholder.typicode.com/photos?_page=${page}&_limit=10`);
          setItems(prevItems => [...prevItems, ...response.data.map((item: any) => item.url)]);
        };
        fetchData();
      }, [page]);
    • useEffect를 사용하여 axios.get 요청을 통해 들어온 데이터가 useState를 사용해 items에 저장을 한다.
    • useEffect의 종속성 배열을 사용하여 page가 바뀔때 호출하도록 되어있다.

     

     useEffect(() => {
        // 스크롤링 감지 이벤트
        window.addEventListener("scroll", handleScroll);
        return () => window.removeEventListener("scroll", handleScroll);
      }, []);
     
     const handleScroll = () => {
        // 스크롤링 감지 콜백 함수 
        const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
        if (scrollTop + clientHeight >= scrollHeight - 100) {
          setPage((prevPage) => prevPage + 1);
        }
      };
    • window.addEventListener()는 첫번째 인자로 "scroll"을 받고 있기 때문에 scroll할 때마다 콜백 함수가 작동한다.
    • window.addEventListener()의 두번째 인자는 콜백 함수이다.
    • 해당 페이지를 떠날 경우 window.removeEventListener()를 사용하여 이벤트를 없앤다.
    • scrollTop은 요소의 가장 위부터 스크롤을 내린 위치의 픽셀 수를 나타낸다.(스크롤을 할 수 없는 경우 0이다.)
    • scrollHeight는 overflow로 보이지 않는 content를 포함한 content의 높이를 나타낸다.
    • clientHeight는 content의 내부 높이를 나타낸다. => overflow로 보이지 않는 부분은 포함되지 않음.
    • 즉, 스크롤 바의 높이 + 보이는 부분의 content 이 전체 content의 높이 - 100보다 크거나 같을 경우 page가 +1된다. => 데이터 받아오는 useEffect가 호출된다. => 데이터가 업데이트 된다. 

     

    3. 적용

    우리 프로젝트의 입맛에 맞게 조금 고쳐서 적용해 봤다!

     const [items, setItems] = useState<any[]>([]); // axios로 받아온 데이터 저장
      const [isPage, setPage] = useState(1); // 현재 페이지 저장
    
      useEffect(() => {
        // 처음 데이터 받아오고 현재 페이지가 바뀔때 데이터 받아오고 items에 저장
        const fetchData = async () => {
          const res = await axios.get(`http://localhost:3001/data/${isPage}`);
          setItems((prev) => [...prev, ...res.data.data]);
        };
        fetchData();
      }, [isPage]);
    
      const handleScroll = () => {
        // 스크롤 위치 감지 콜백 함수 
        const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
        // 마지막 페이지 조건 추가
        if (scrollTop + clientHeight >= scrollHeight - 500) {
          setPage((prev) => prev + 1);
        }
      };
    
      useEffect(() => {
        window.addEventListener("scroll", handleScroll);
        return () => window.removeEventListener("scroll", handleScroll);
      }, []);

    출처: https://abangpa1ace.tistory.com/118

     

Designed by Tistory.