티스토리 뷰

무한 스크롤 이란? 무한 스크롤은 무한히 스크롤링을 기능하는 기능을 말한다. 페이지를 클릭하면 다음 페이지 주소로 이동하는 pagenation과 달리 페이지 하단에 도달하면 새로운 컨텐츠가 한 화면에 추가로 로드된다.

 무한 스크롤의 장점

1. 사용자 참여 및 콘텐츠 탐색이 쉽습니다.

2. 무한 스크롤이 클릭하는 것보다 더 나은 사용자 경험을 제공합니다.

=> 따라서 다음 콘텐츠를 보기 위한 추가 클릭이 필요없고 페이지 로드 시간이 짧습니다.

3. 터치 스크린(모바일) 일때 더 유용하게 적용됩니다.

=> 화면이 작을수록 스크롤이 길어지기에 모바일 환경에서 콘텐츠를 보여주기 직관적이고 사용하기 쉽습니다.

 무한 스크롤의 단점

1. 페이지 성능이 느려집니다.

2. 특정 항목 검색 및 원래 위치로 돌아오기 힘듭니다.

3. 스크롤 막대가 실제 데이터양을 반영하지 못합니다.


 구현방식

화상채팅 스터디 프로젝트를 구현하는 와중에, 채팅이나 스터디 목록을 불러오는 와중에 사용자 입장에서 스크롤을 할때마다 정해진 수만 나오는 것이 아닌 바로바로 업데이트 되어 보여주는 화면이 필요하다 생각하였습니다. 그래서 무한 스크롤을 생각하였고, 구현 방식으로는 두가지 방법을 생각하였습니다. 크게, OnScroll / Intersection Obeserver API 두가지 방법으로 나누어 구현하였습니다.

1. OnScroll

OnScroll이란? 유저가 scroll을 하면 이벤트가 발생하고 현재 scroll 위치가 페이지에 끝에 닿았는지 판단한다. 하지만 이 경우 scroll 이벤트는 굉장히 빈번하게 발생하기 때문에 성능 최적화에 위배되는 문제가 있다. 그렇기 때문에 throttle 작업이 추가적으로 필요하다.
function App() {
  const { data, hasNextPage, fetchNextPage } = useInfiniteQuery(
    'repositories',
    ({ pageParam = 1 }) => fetchRepo(pageParam),
     //fetchRepo 함수를 호출하며, pageParam은 페이지 번호(1)를 나타냄
    {
      getNextPageParam: (lastPage, allPages) => {
        const nextPages = lastPage.total_count / 30;
        const nextPage = allPages.length + 1;
        return nextPage <= nextPages ? nextPage : undefined;
        //getNextPageParam 함수를 정의하며, 다음 페이지의 매개변수를 계산하는 역할
        //lastPage는 이전 페이지의 데이터를 나타내며, allPages는 모든 페이지의 데이터 배열
        //nextPages는 마지막 페이지의 총 항목을 30으로 나누어 다음 페이지의 총 페이지 수를 계산
        //nextPage는 현재 로드된 모든 페이지의 수에 1을 더함
      },
    }
  );

repositories 은 쿼리의 식별자로 식별자를 통해 캐싱을 진행합니다.

이후에 fectchRepo 함수를 호출하여 각 페이지 번호를 나타내고, 1이므로 첫번째 페이지를 가져옵니다.

getNextPageParam 함수를 정의하여 다음 페이지를 계산합니다.
- lastPage가 이전 페이지의 데이터를 나타내며, allPages는 모든 페이지를 나타냅니다.

- nextPages는 마지막 페이지의 총 항목을 30으로 나누어 다음 페이지의 총 페이지 수를 계산하여 나타냅니다.

- nextPage는 현재 로드된 모든 페이지의 수에 1을 더해 다음 페이지의 수를 나타냅니다.

후에 다음 페이지 번호가 총 페이지수를 넘지 않으면 다음 페이지 번호를 반환, 그렇지 않으면 undefined를 반환하여 스크롤에 끝에 도달시 무한스크롤이 종료됩니다.

  useEffect(() => {
    let fetching = false;

    const onScroll = async (event) => {
      const { scrollHeight, scrollTop, clientHeight } =
        event.target.scrollingElement;

      if (!fetching && scrollHeight - scrollTop <= clientHeight * 1.5) {
        fetching = true;
        if (hasNextPage) await fetchNextPage();
        fetching = false;
      }
    };
    document.addEventListener('scroll', onScroll);
    return () => {
      document.removeEventListener('scroll', onScroll);
    };
  }, []);
  console.log(data);

onScroll 함수를 정의하여, 스크롤 이벤트가 발생할 때 호출됩니다.

- const { scrollHeight, scrollTop, clientHeight } = event.target.scrollingElement;

=> 스크롤 요소의 높이, 스크롤 위치 및 클라이언트 창의 높이를 추출하여, 무한 스크롤을 구현하는 데 사용됩니다.

- !fetching && scrollHeight - scrollTop <= clientHeight * 1.5)

=> fetching이 false이고 사용자가 스크롤을 아래로 움직여 스크롤이 화면의 하단 1.5배 이하에 도달하면 다음 페이지의 데이터를 가져오도록 조건을 검사합니다.

- if (hasNextPage) await fetchNextPage();

=> hasNextPage 변수가 true이면 fetchNextPage 함수를 호출하여 다음 페이지의 데이터를 가져옵니다.

- document.addEventListener('scroll', onScroll);

=> 스크롤 이벤트가 발생할 때마다 onScroll 함수를 호출하기 위해 스크롤 이벤트 리스너를 추가합니다.

- return () => { document.removeEventListener('scroll', onScroll);

=> 컴포넌트가 언마운트되거나 업데이트될 때 스크롤 이벤트 리스너를 제거합니다.

 

이 코드에서 중요시 알아야할 scrollHeight, scrollTop, clientHeight 세가지로 나누어 자세히 설명하겠습니다.

 

scrollHeight

첫 번째 속성은 scrollHeight 입니다. document.documentElement.scrollHeight;와 같이 사용하면 해당 페이지의 전체 높이가 스크롤 높으로 측정이 되며, 컴포넌트의 높이를 사용하고 싶다면 useRef를 이용하면 됩니다. 변수.current.scrollHeight 와 같은 모양이 될 것입니다.

scrollTop

두 번째 속성은 scrollTop 입니다. 현재 스크롤에서 가장 상단의 위치를 알려줍니다. 스크롤을 끝까지 내린다면, scrollTop값은 scrollHeight가 될 것 같지만 자신의 높이만큼 빼줘야합니다. 왜냐하면 아무리 가장 아래로 스크롤이 내려갔더라도 현재 스크롤의 위치는 바닥에서 해당 컴포넌트 UI의 높이만큼 위에있기 때문입니다.

clientHeight

clientHeight는 클라이언트의 뷰 높이입니다. 스크롤을 가장 아래로 내렸을 때, scrollHeight - scrollTop을 하면 클라이언트가 현재 보고있는 웹 사이트의 크기값 입니다. 예를 들어, 내가 누군가의 블로그의 글을 중간 정도 까지 읽는중이라고 가정하면. 이 경우 스크롤은 당연히 중간에 있습니다. 그러면 scrollHeight - scrollTop의 값은 내가 보고있는 블로그 화면의 크기보다 훨씬 클 것이지만 끝까지 내린다면 이 값은 같아지게 되는 것입니다.

    if (scrollHeight - scrollTop  >= clientHeight) {
      moreDataFetch()
    }

2. Intersection Obeserver API 

IntersectionObserver란? IntersectionObserver은 인터페이스는 대상 요소와 그 상위 요소 혹은 최상위 도큐먼트인
viewport와의 교차 영역에 대한 변화를 비동기적으로 감지할 수 있도록 도와준다.
const observer = useRef(null);
  const observerCallback = (entries) => {
    const entry = entries[0];
    if (entry.isIntersecting && hasNextPage && !isFetching) {
      fetchNextPage();
    }
  };

  useEffect(() => {
    observer.current = new IntersectionObserver(observerCallback, {
      threshold: 1.0,
    });

    if (observer.current) {
      observer.current.observe(document.querySelector('#loadMoreTrigger'));
    }

    return () => {
      if (observer.current) {
        observer.current.disconnect();
      }
    };
  }, [hasNextPage, isFetching, fetchNextPage]);

const observer = useRef(null);

=> useRef를 사용하여 IntersectionObserver 객체를 참조하는 observer 변수를 생성합니다. 이 변수는 함수 컴포넌트의 렌더링 사이에 지속적으로 유지됩니다.

const observerCallback = (entries)

=> observerCallback 함수를 정의하고, IntersectionObserver의 콜백 함수로 사용되며, 관찰된 엘리먼트의 진입 여부를 확인하고 조건에 따라 데이터를 가져오는 역할을 합니다.

entries[0] => entries 배열에는 관찰된 엘리먼트에 대한 정보가 포함되어 있습니다.

useEffect(() => ... [hasNextPage, isFetching, fetchNextPage]);

=> useEffect 훅을 사용하여 무한 스크롤을 관리하는 부분을 정의하며, hasNextPage, isFetching, 및 fetchNextPage가 포함되어 있으므로 이 값들 중 하나라도 변경될 때마다 useEffect가 실행됩니다.

observer.current = new IntersectionObserver(observerCallback, { threshold: 1.0 });

=> IntersectionObserver 객체를 생성하고 observerCallback 함수를 콜백으로 설정합니다. threshold: 1.0 옵션은 엘리먼트가 화면에 100% 보일 때 콜백이 호출되도록 설정합니다.


무한 스크롤 구현하며 느낀점 및 성능 최적화

항상 무한스크롤의 구현방식에 대해서 궁금했었는데, 이번 기회에 구현하며 2가지 방식의 차이가 있다는것을 알수 있었다.

페이지 성능이 느려지는 단점을 극복하기위해 생각 할 수 있는 기회가 되었으며, onscroll 방싱의 경우 throttle를 사용하면 최적화가 가능하다는 사실과 사실 onscroll + throttle 방식보다 IntersectionAPI 방식이 최적화 방면에서는 더 좋아 많이 사용한다는 사실을 알았다.

스크롤 하는 방식에서 callback 하는 수의 따라서 속도나 최적화 성능이 달라지는것 같다.

 

 

참고 : https://velog.io/@leehyunho2001/%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95%EA%B3%BC-%EA%B3%A0%EB%A0%A4%ED%95%A0-%EC%82%AC%ED%95%AD

'FrontEnd > React' 카테고리의 다른 글

8장. Hooks  (0) 2023.03.19
7장. 컴포넌트의 라이프사이클 메서드  (0) 2023.03.11
6장. 컴포넌트 반복  (2) 2023.03.03
5장. ref : DOM 에 이름 달기  (0) 2023.02.18
4장. 이벤트 핸들링  (0) 2023.02.11
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30