Infinite scroll(무한 스크롤)이란?
사용자가 페이지 하단에 도달했을 때, 콘텐츠를 추가로 제공하는 것이다. 사용자 입장에서 페이지를 따로 넘겨야 할 필요가 없어 편리하다.
구현할 수 있는 방법이 여러 가지였다.
- scroll
- getBoundingClientRect()
- Intersection Observer
- useRef
scroll
이 방식은 스크롤할 때마다 이벤트가 발생하므로 throttle(일정 시간 동안의 이벤트 실행을 막고, 입력을 모아서 일정 시간이 지날 때마다 한 번씩 출력하는 것)로 성능 최적화를 해줘야 한다.
scroll을 이용한 구현 방법은 아래에 잘 설명되어있다.
getBoundingClientRect()
특정 element가 현재 화면에 보이는지 검사할 때 사용하는 Element.getBoundingClientRect 함수를 쓰는 방식은 다음과 같은 문제점이 있다.
- 이 함수를 호출할 때마다 element의 크기와 위치 값을 최신 정보로 알기 위해 일부나 전체를 다시 그리게 되는 리플로우(reflow) 현상이 발생한다.
- 모든 코드가 메인 스레드에서 실행되기 때문에 성능 문제를 일으킬 수 있다.
Intersection observer
MDN에 따르면,
Intersection Observer API는 타겟 요소와 상위 요소 또는 최상위 document의 viewport사이의 intersection 내의 변화를 비동기적으로 관찰하는 방법
이라고 한다.
즉, 보이는 화면 상에 내가 지정한 타켓 Element가 보이고 있는지 관찰해주는 역할을 한다.
Intersection observer 생성하기
const options = {
root: null,
rootMargin: "20px",
threshold: 0.1,
};
const observer = new IntersectionObserver(handleIntersect, options);
- root : 이 값을 기준으로 타겟 Element가 보이는지를 관찰한다. 기본값은 브라우저 viewport이다.
- rootMargin : root에 정의된 Element가 가진 마진값이다.
- threshold : 얼마만큼 보였을 때 callback함수를 실행할지를 결정한다. 만약 50%만큼 보여졌을 때 실행하고 싶다면, 값을 0.5로 설정하면 된다.
타겟 Element 설정하기
맨 아래 div로 타겟을 만들어 주고 useRef로 targetRef를 넣어준다.
const targetRef = useRef(null);
return (
<section className="container">
{isLoading ? (
<Loading />
) : (
<>
<ul className="posts">
{posts.map((post) => (
<Post key={post.id} post={post} />
))}
</ul>
</>
)}
<div className="target" ref={targetRef}></div>
</section>
);
🎉완성!!🎉
전체 소스
import React, { useCallback, useEffect, useRef, useState } from "react";
import axios from "axios";
import Post from "./post";
import Loading from "./loading";
const Posts = () => {
const targetRef = useRef(null);
const [page, setPage] = useState(1);
const [isLoading, setIsLoading] = useState(true);
const [posts, setPosts] = useState([]);
// posts data를 받아오는 함수
// 한 페이지에 10개씩 받아오도록
const getPosts = useCallback(async (page) => {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=10`
);
setPosts((prev) => [...prev, ...response.data]);
setIsLoading(false);
}, []);
const handleIntersect = useCallback((entries) => {
//entries배열의 첫번째 요소로 target
const target = entries[0];
// target이 보이는 경우
if (target.isIntersecting) {
setPage((prev) => prev + 1);
}
}, []);
useEffect(() => {
getPosts(page);
}, [getPosts, page]);
useEffect(() => {
const options = {
root: null,
rootMargin: "20px",
threshold: 0.1,
};
const observer = new IntersectionObserver(handleIntersect, options);
if (targetRef.current) {
// 관찰할 대상을 등록
observer.observe(targetRef.current);
}
return () => observer.disconnect();
}, [handleIntersect]);
return (
<section className="container">
{isLoading ? (
<Loading />
) : (
<>
<ul className="posts">
{posts.map((post) => (
<Post key={post.id} post={post} />
))}
</ul>
</>
)}
<div className="target" ref={targetRef}></div>
</section>
);
};
export default Posts;
https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API
'IT > React' 카테고리의 다른 글
댓글 대댓글 추가하기 (0) | 2022.04.12 |
---|---|
[TIL] React에 ESLint와 Prettier 설정 (0) | 2022.02.28 |
nodejs 버전 변경 (0) | 2022.02.14 |
React에서 resize (0) | 2022.02.13 |
React로 무한 캐러셀(Carousel) 만들기 (0) | 2022.01.23 |