본문 바로가기
  • 살짝 구운 김 유나
Project/zum:go

[React] react-Intersection-Observer 라이브러리를 이용해 무한스크롤 구현하기

by yunae 2023. 2. 7.

InterSection Observer?

=> 브라우저 Viewport와 설정한 요소의 교차점을 관찰하여 요소가 viewport에 포함되는지 구별하는 기능 제공

  • 페이지 스크롤 시 이미지를 Lazy-Loading 할 때
  • 무한 스크롤을 통해 새로운 컨텐츠를 불러올 때
  • 광고의 수익을 계산하기 위해 광고의 가시성을 참고할 떄
  • 사용자가 결과를 볼 것인지에 따라 애니메이션 동작 여부를 결정할 때



무한 스크롤

사용자가 특정한 영역 하단에 도달했을 때, API가 호출되며 콘텐츠가 계속 로드되는 방식
-> 모든 데이터를 불러와 뿌려주는 방식은 렌더링 시간이 많이 소요되니까 ㅜ 바닥에 닿으면 불러오자!

구글에 정보가 정말 많아서 라이브러리 없이도 구현 해보면 좋을 것 같아,,
하지만,, 나는 당장 시간이 좀 없기 때무네,, 라이브러리를 잘 써보려고 한다,,ㅎ

react-intersection-observer

관찰하는 객체(observer) 하나를 ref로 설정한 후 해당하는 객체가 화면에 보이면 특정 코드를 실행시킬 수 있다.
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

 

설치

npm install react-intersection-observer --save



Import

import { useInView } from 'react-intersection-observer';

 

사용하기

  const [ref, inView] = useInView();
  
  ...
  
  return (
  	// 관찰할 객체에 ref를 달아준다.
  	<div ref={ref}></div>
  )

나는 scroll area의 하단에 "안녕"이라고 붙여 놓았다.

<div className={styles.scrollarea}>
          {products?.map((product) => {
            return (
              <ProductItem
                key={product.productId}
                product={product}
                clickProduct={clickProduct}
              />
            );
          })}
          <div ref={ref}>안녕</div>
        </div>

콘솔창에 출력해볼 수 있다.

console.log(inView, '무한 스크롤 요청 🎃')





스크롤 시에 ref가 노출되면 특정 함수 실행하기

 // 무한 스크롤
  // 지정한 타겟 div가 화면에 보일 때 마다 서버에 요청을 보냄
  const productFetch = () => {
    axios
    .get(`https://localhost:8080/products/main?pageNo=${page}&pageSize=5`)
    .then((res) => {
      // 리스트 뒤로 붙여주기
      setProducts([...products, ...(res.data)])
      // 요청 성공 시에 페이지에 1 카운트 해주기
      setPage((page) => page + 1)
    })
    .catch((err) => {console.log(err)});
  };

- 서버단에 페이지 번호와 사이즈를 넘겨주기로 약속했다.
- 더이상 페이지가 없으면 요청을 하지 않도록 하는 방식도 생각해보면 좋을 것 같다,, 근데 이제 그게 지금은 아닌,,ㅎ

useEffect(() => {
    // inView가 true 일때만 실행한다.
    if (inView) {
      console.log(inView, '무한 스크롤 요청 🎃')

	  // 실행할 함수
      productFetch();
    }
    
  }, [inView]);




 

전체 코드

import React, { useEffect, useState } from "react";
import HomeBanner from "../components/Home/HomeBanner";
import BottomNav from "../components/Nav/BottomNav";
import TopNav from "../components/Nav/TopNav";
import z from "../assets/images/z.png";
import styles from "./styles/Home.module.css";

import { useNavigate } from "react-router-dom";
import ProductItem from "../components/Product/ProductItem";
import axios from "axios";
import { useInView } from 'react-intersection-observer';

export default function Home() {
  const navigate = useNavigate();
  const [products, setProducts] = useState([]);
  const [page, setPage] = useState(0); // 현재 페이지 번호 (페이지네이션)
  const [ref, inView] = useInView();

  // 무한 스크롤
  // 지정한 타겟 div가 화면에 보일 때 마다 서버에 요청을 보냄
  const productFetch = () => {
    axios
    .get(`https://localhost:8080/products/main?pageNo=${page}&pageSize=5`)
    .then((res) => {
      console.log(res.data);
      // 리스트 뒤로 붙여주기
      setProducts([...products, ...(res.data)])
      // 요청 성공 시에 페이지에 1 카운트 해주기
      setPage((page) => page + 1)
    })
    .catch((err) => {console.log(err)});
  };

  useEffect(() => {
    // inView가 true 일때만 실행한다.
    if (inView) {
      console.log(inView, '무한 스크롤 요청 🎃')

      productFetch();
    }
  }, [inView]);

  const clickProduct = (id) => {
    navigate(`/detail/${id}`);
  };

  return (
    <div className={styles.background}>
      <TopNav />

      <HomeBanner />
      <div className={styles.body}>
        <div className={styles.onsale}>판매중</div>
        <div className={styles.scrollarea}>
          {products?.map((product) => {
            return (
              <ProductItem
                key={product.productId}
                product={product}
                clickProduct={clickProduct}
              />
            );
          })}
          <div ref={ref}>안녕</div>
        </div>
        <BottomNav/>
      </div>
    </div>
  );
}

실행 결과

"안녕" div가 하단 고정 네비에 가려져 보이지는 않지만, inView는 true로 잘 동작하는 것을 확인할 수 있다





댓글