새로운 프로젝트에 채팅 기능이 추가될 예정입니다. 채팅기능에 필요한 기능 중 하나가 Infinite 스크롤이라, React스럽게 구현해보도록 하겠습니다.
원리는 간단합니다. 스크롤이 되는 영역이 있을 때, 스크롤바의 높이가 0이 되면, 데이터를 불러와서 기존의 리스트에 추가하면 됩니다.
데이터를 다루는 custom Hook인 UseFetch 를 먼저 확인해봅니다.
const UseFetch = <T,>(url: string) => {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const getData = async () => {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("Network response was not ok");
}
const jsonData = await response.json();
setData(jsonData);
setLoading(false);
} catch (error) {
console.error("Error fetching data:", error);
setLoading(false);
}
};
useEffect(() => {
getData();
}, [url]);
return { data, loading };
};
이 Hook을 이용하면 좋을 것 같으나, 기존 data에 jsonData (둘다 array) 를 추가하는 부분에서 타입이 같다는 걸 확인할 수가 없어서 어떻게 해줄수가 없었습니다... (해결방법좀.. ㅠㅠ)
그래서 거의 동일한 형태에 Array 만 다루는 훅을 새로 만들어줬습니다.
const UseArrayFetch = <T,>(url: string) => {
const [data, setData] = useState<T[]>([]);
...
const getData = async () => {
...
setData([...data, ...jsonData]);
};
return { data, loading };
};
달라진 부분은 setData에서 기존의 Data에 추가하는 부분입니다.
url 이 달라지면, 새롭게 데이터를 불러오게 됩니다.
const CommentPage = () => {
const [page, setPage] = useState(1);
const { data } = UseArrayFetch<Comment>(CommentApi.getList(page));
const refetch = () => setPage((prev) => prev + 1);
return (
<section>
{data && <CommentList comments={data} refetch={refetch} />}
</section>
);
};
pagination 의 page를 state 로 관리하고, 이 값을 하나씩 늘려가면, 데이터를 추가하는 방법입니다.
const CommentList = ({ comments, refetch }: Props) => {
const ELEMENT_ID = "comment_infinite_list";
return (
<Wrapper id={ELEMENT_ID}>
{comments.map((comment) => (
<CommentItem key={comment.id} comment={comment} />
))}
</Wrapper>
);
export default CommentList;
이 CommentList 안에서 스크롤을 이용할 것입니다. id를 상수화해서 보기 쉽게 만들어줍니다.
const UseInfiniteScroll = ({ refetch, elementId }: Props) => {
useEffect(() => {
const handleScroll = () => {
const { offsetHeight, scrollTop, scrollHeight } = document.getElementById(
elementId
) as HTMLElement;
if (scrollHeight <= offsetHeight + scrollTop) refetch();
};
const $element = document.getElementById(elementId) as HTMLElement;
$element.addEventListener("scroll", handleScroll);
return () => $element.removeEventListener("scroll", handleScroll);
}, []);
};
원리는 이 컴포넌트가 render 되게 되면, 받아온 elementId 를 찾아 스크롤 이벤트를 시작하고, 없어지면 끝냅니다.
즉
3m 짜리의 줄이 있고 (scrollHeight)
우리가 보이는건 1m 이고 (offsetHeight)
지금 있는 높이는 1.3m 라고 생각해봅시다(scrollTop)
스크롤 다운을 해서 (맨 위 0, 내려갈수록 scorollTop up) scrollTop이 2m 가 되면 3m 짜리의 바닥에 닿은 것입니다.
=> (scrollHeight <= offsetHeight + scrollTop) refetch()
이해완료