React를 사용하여 무한 스크롤(infinite scroll) 기능을 구현하는 방법에 대해 공부해보자. 무한 스크롤은 사용자가 페이지를 스크롤할 때 추가 컨텐츠를 동적으로 로드하는 기능입니다. 이를 통해 사용자 경험을 향상시키고 페이지의 성능을 최적화할 수 있습니다.
Intersection Observer란?
Intersection Observer는 웹 페이지에서 요소의 가시성을 관찰하고 이벤트를 트리거하는 기능을 제공하는 웹 API입니다. 이 API는 스크롤, 뷰포트 크기 변경, 요소의 크기 변경 등과 같은 요소의 가시성 변경을 모니터링할 수 있습니다. 주로 무한 스크롤, 이미지 로딩 지연, 광고 표시 등 다양한 웹 페이지 기능에서 활용됩니다.
Intersection Observer의 주요 메서드:
- observe(target): 관찰 대상 요소를 관찰 대상 목록에 추가합니다.
- unobserve(target): 관찰 대상 목록에서 요소를 제거하여 관찰을 중단합니다.
- disconnect(): Intersection Observer를 해제하고 모든 관찰 대상을 중지합니다.
1. 기본 코드 구조
먼저, React 애플리케이션에서 무한 스크롤을 구현하는 기본적인 코드 구조를 생성합니다.
import React, { useEffect, useRef, useState } from 'react';
function ProductList() {
const [products, setProducts] = useState([]);
const [hasMore, setHasMore] = useState(true);
const [page, setPage] = useState(0);
const elementRef = useRef(null);
// Intersection Observer 콜백 함수와 useEffect를 활용한 코드는 이후에 추가.
return (
<>
{/* 상품 목록 및 무한 스크롤 요소 */}
</>
);
}
export default ProductList;
2. Intersection Observer를 사용한 스크롤 감지
무한 스크롤을 위해 사용자의 스크롤 동작을 감지하기 위해 Intersection Observer를 활용합니다. 이로써 사용자가 화면 하단에 도달할 때마다 추가 데이터를 불러옵니다.
const onIntersection = (entries) => {
const firstEntry = entries[0];
// 첫 번째 entry가 화면에 나타나고 더 많은 데이터를 불러올 수 있는 상태(hasMore)인 경우 fetchMoreItems 함수를 호출.
if (firstEntry.isIntersecting && hasMore) {
fetchMoreItems();
}
};
// 컴포넌트 렌더링 이후에 실행되며 Intersection Observer를 설정
useEffect(() => {
const observer = new IntersectionObserver(onIntersection);
//elementRef가 현재 존재하면 observer로 해당 요소를 관찰.
if (elementRef.current) {
observer.observe(elementRef.current);
}
// 컴포넌트가 언마운트되거나 더 이상 관찰할 필요가 없을 때(observer를 해제할 때)반환.
return () => {
if (elementRef.current) {
observer.unobserve(elementRef.current);
}
};
}, [hasMore]);
- onIntersection 함수 : Intersection Observer 콜백 함수로, 화면에 나타날 때 추가 데이터를 불러오기 위한 역할.
- 특정 요소가 화면에 나타날 때마다 추가 데이터를 불러오도록 설정하며, hasMore 상태를 확인하여 더 이상 불러올 데이터가 없을 때 스크롤 이벤트를 중단.
3. 추가 상품 불러오기
무한 스크롤을 구현하려면 추가 상품을 비동기적으로 불러와야 합니다.
const fetchMoreItems = async () => {
// 새로운 데이터를 불러올 API 엔드포인트에 요청을 보냅니다.
const response = await fetch(
`https://dummyjson.com/products?limit=10&skip=${page * 10}`
);
// 응답 데이터를 JSON 형식으로 파싱합니다.
const data = await response.json();
// 만약 더 이상 불러올 상품이 없다면 hasMore 상태를 false로 설정합니다.
if (data.products.length === 0) {
setHasMore(false);
} else {
// 불러온 데이터를 현재 상품 목록에 추가합니다.
// 이전 상품 목록(prevProducts)에 새로운 데이터(data.products)를 연결합니다.
setProducts((prevProducts) => [...prevProducts, ...data.products]);
// 페이지 번호를 업데이트하여 다음 요청에 올바른 skip 값을 사용합니다.
setPage((prevPage) => prevPage + 1);
}
};
fetchMoreItems 함수는 Intersection Observer를 통해 화면 하단에 스크롤할 때마다 새로운 상품 데이터를 비동기적으로 불러오고 상태를 업데이트하는 역할.
4. 컴포넌트 렌더링
컴포넌트에서 상품 목록을 렌더링하고, 무한 스크롤 요소를 추가합니다.
return (
<>
{products.map((item, index) => (
<Card
key={index}
style={{ width: '600px', margin: '0 auto' }}
className={'mb-2'}
>
<Row>
<Col md={4}>
<img
src={item.thumbnail}
alt="상품 이미지"
style={{ width: '100%', margin: '10px' }}
/>
</Col>
<Col md={8}>
<Card.Body>
<Card.Text>{item.description}</Card.Text>
<Card.Text>${item.price}</Card.Text>
</Card.Body>
</Col>
</Row>
</Card>
))}
{hasMore && (
<div ref={elementRef} style={{ textAlign: 'center' }}>
Load More Items
</div>
)}
</>
);
🌲 프로젝트 트리 구조
.
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
│ └── vite.svg
├── src
│ ├── @type
│ ├── App.tsx
│ ├── components
│ │ ├── Product
│ │ │ └── ProductList.tsx
│ │ └── UI
│ ├── index.css
│ ├── main.tsx
│ ├── style
│ └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
⭐️ 구현 결과
📚 참고
'React' 카테고리의 다른 글
React-Hook-Form 이란? (0) | 2023.07.05 |
---|---|
[Next JS / React] Next JS에서 SVG 사용법 (0) | 2023.06.27 |
Redux의 3원칙 (0) | 2023.04.06 |
Recoil 주요 개념 (0) | 2023.03.31 |
Recoil 기본 개념 (1) | 2023.03.31 |