리액트에서 무한스크롤을 구현하는 방식은 Scroll event
를 사용하거나
Observer API
를 이용한 방식 또는 잘 만들어진 라이브러리
를 사용하는 방법들이 있다.
그중 가장 보편적인 Scroll event 를 사용하여 쉽고 빠르게 구현했다.
const [data, setData] = useState([])
const [page, setPage] = useState(10)
const getData = async () => {
return await axios
.get(`http://kellystorage.shop/search?idx=${page}`)
.then((res) => {
let reverseData = res.data.data.reverse()
setData(data.concat(reverseData))
setPage((pre)=>pre+10)
})
.catch((error)=>{
alert(error)
})
};
나는 axios를 사용했고 다양한 방법이 있지만 백엔드와의 약속에서 url 파라미터로 10을 보낼경우 0~9까지의 리스트를 20을 보낼경우 10~19까지의 리스트를 보내준다.
이 함수는 useEffect 를 이용해 렌더링시 최초로 한번 실행해 줄것이며 그 후에 스크롤이 바닥에 닿았을 경우 실행해준다. 그렇게 받은 리스폰 데이터를 setData와 .cocat(data)를 이용해 배열에 추가시켜준다.
(.reverse()는 백에서 데이터배열을 거꾸로 잘못 보내줘서 추가했다.)
그리고 다음 데이터를 받기위해 setPage((pre)=>pre+10) 를 이용해 파라미터로 사용할 데이터를 저장해준다.
그렇다면 스크롤이 바닥에 닿는 것을 어떤 방법으로 감지할까 ? 바로 스크롤 이벤트를 이용한다.
import { throttle } from 'lodash';
const handleScroll = throttle(() => {
const scrollHeight = document.documentElement.scrollHeight;
const scrollTop = document.documentElement.scrollTop;
const clientHeight = document.documentElement.clientHeight;
if (scrollTop + clientHeight >= scrollHeight) {
getData() // api 통신 함수
}
}, 300);
우선 스크롤이 바닥에 닿으면 실행될 함수를 만든다.
아래 사진을 보면
clientHeight = 사용자가 지금 보는 높이
scrollTop = 사용자가 보는 페이지와 원래 페이지의 최상단과의 차이
scrollHeight = 화면의 높이값
즉 스크롤이 끝까지 내려가면 (scrollTop + clientHeight >= scrollHeight) 이라는 식이 성립된다 .
if 문을 이용해 처음 만들어둔 api 통신 함수를 실행시켜준다.
(api 통신 함수를 바로 넣어줘도 되지만 따로 만든 이유는 추후 스크롤 이벤트만 커스텀훅으로 바꿔주기 위함이다.)
쓰로틀로 최적화해주었다.
이렇게 최종적으로 스크롤이 바닥에 닿았을 경우 실행될 함수 handleScroll()가 완성되었다.
그렇다면 우리는 addEventListener를 이용해 스크롤을 감시해야한다.
useEffect(() => {
getData()
}, []); /// 최초 렌더시 필요한 데이터
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, [handleScroll]);
다른 페이지에서도 무한스크롤이 필요했기에 반복적으로 사용할 함수 handleScroll()를 커스텀 훅으로 만들어준다.
import { useEffect, useState } from "react";
import { throttle } from 'lodash';
const useInfiniteScroll = ({ onScrollEnd }) => {
const [isEnd, setIsEnd] = useState(false);
const handleScroll = throttle(() => {
const scrollHeight = document.documentElement.scrollHeight;
const scrollTop = document.documentElement.scrollTop;
const clientHeight = document.documentElement.clientHeight;
if (scrollTop + clientHeight >= scrollHeight) {
setIsEnd(true);
if (onScrollEnd)
onScrollEnd(); /// api 통신함수
setIsEnd(false);
}
}, 300);
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, [handleScroll]);
return { isEnd };
};
export default useInfiniteScroll;
중복사용할 scroll event 함수들을 hook으로 만들어주었다.
onScrollEnd() 함수가 파라미터로 받아온 api통신 함수 getData()이다.
useState(false)를 하나 만들어 통신 전에 setIsEnd(true)를 통신 후에 setIsEnd(false)를 해준다. 이 값을 가지고 훅을 사용하는 컴포넌트에서 활용할 수 있다.
const [data, setData] = useState([])
const [page, setPage] = useState(10)
const getData = async () => {
return await axios
.get(`http://kellystorage.shop/search?idx=${page}`)
.then((res) => {
let reverseData = res.data.data.reverse()
setData(data.concat(reverseData))
setPage((pre)=>pre+10)
})
.catch((error)=>{
alert(error)
})
};
const { isEnd } = useInfiniteScroll({ onScrollEnd: getData });
///onScrollEnd 이란 이름으로 getData() 함수를 useInfiniteScroll hook에 보낸다.
useEffect(() => {
getData()
}, []);
return (
<ResultList>
{data.map((item)=>
<ResultItem key={item.id}>
</ResultItem>)}
{isEnd && <div>...로딩중</div>}
</ResultList>
)