무한 스크롤 페이지 만들기
하지만, 스크롤 이벤트로 무한 스크롤을 구현한다면, 리플로우에 의해 좋지 않은 렌더링 성능과 상황에 따라 기대한 대로 동작하지 않을 수 있다. 이를 해결하기 위해 나온것이 Intersection Observer API이다.
스크롤 이벤트로 무한 스크롤을 구현하면 안좋은 점
영향을 받는 모든 요소에 대해 필요한 정보를 축적하기 위해 이벤트 처리기와 getBoundingClientRect()와 같은 메서드를 호출하는 루프를 호출하면, 이는 모두 메인 스레드에서 실행되기 때문에 이 중 하나라도 성능 문제를 일으킬 수 있다.
Intersection Observer를 이용해서 무한 스크롤을 구현하기 위해서는 root, observer, 대상이 필요하다.
관찰자를 생성하기 위해서는 생성자를 호출하고, 교차될때 실행할 callback 함수를 전달하려 생성해야 한다.
내가 정리한 이용 방법은 아래와 같다.
1. 생성자를 통해 콜백 함수와 options를 넣은 하나의 관찰 함수를 생성한다.
2. 관찰할 요소를 observer.observe()로 지정한다.
3. 지정된 요소가 root와 교차하면, callback 함수가 실행된다.
4. callback함수에 교차 상태를 알려주는 entry 객체 목록이 들어온다.
5. entry 각 객체에서 isIntersecting의 불리언 유무를 통해 교차하고 있으면, 필요한 데이터를 가져오는 함수를 실행시킨다.
interface DataMock {
isEnd: boolean;
data: MockData[];
}
function App() {
const [page, setPage] = useState<number>(0);
const [currentData, setCurrentData] = useState<MockData[]>([]);
const [isEnd, setIsEnd] = useState(false);
const observerRef = useRef(null);
const getData = useCallback(
// 3. 콜백 함수의 인자로 entries 객체 array를 받음. 각 entry는 현재 교차 상태인지 알려주는 값들을 담고 있음.
async (entries: IntersectionObserverEntry[]) => {
if (isEnd) return;
if (entries[0].isIntersecting && !isEnd) {
const result = async () => await getMockData(page);
const { data, isEnd }: DataMock = await result();
if (data.length) {
setPage((prev) => prev + 1);
setCurrentData((prev) => [...prev, ...data]);
}
if (isEnd) setIsEnd(true);
}
},
[page, isEnd]
);
useEffect(() => {
const options = {
root: null,
rootMargin: "0px",
threshold: 0.5,
};
const observer = new IntersectionObserver(getData, options); // 1. 생성자를 통해 관찰자 생성
if (observerRef.current) observer.observe(observerRef.current); // 2. ref값이 생기면, 바로 관찰 시작.
return () => {
if (observerRef.current) {
observer.unobserve(observerRef.current);
}
};
}, [getData, isEnd]);
const totalNum = useMemo(() => {
return currentData.reduce((acc, cur) => {
return (acc += cur.price);
}, 0);
}, [currentData]);
return (
<>
<div className="totalNum">{totalNum}</div>
<ul className="box">
{currentData.length &&
currentData.map((el) => {
return (
<li className="boxElement" key={el.productId}>
{el.productName}
</li>
);
})}
</ul>
<div ref={observerRef}>observer</div>
</>
);
}
export default App;
