Infinite Scroll 구현

heyhey·2023년 9월 20일
0

react

목록 보기
17/17
post-thumbnail

새로운 프로젝트에 채팅 기능이 추가될 예정입니다. 채팅기능에 필요한 기능 중 하나가 Infinite 스크롤이라, React스럽게 구현해보도록 하겠습니다.

원리는 간단합니다. 스크롤이 되는 영역이 있을 때, 스크롤바의 높이가 0이 되면, 데이터를 불러와서 기존의 리스트에 추가하면 됩니다.
데이터를 다루는 custom Hook인 UseFetch 를 먼저 확인해봅니다.

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를 상수화해서 보기 쉽게 만들어줍니다.

UseInfiniteScroll

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 를 찾아 스크롤 이벤트를 시작하고, 없어지면 끝냅니다.

  • scrollHeight 는 요소의 전체 높이를 나타냅니다.
  • offsetHeight는 요소의 실제 높이를 나타냅니다.
  • scrollTop 은 현재 스크롤 위치를 나타냅니다.


3m 짜리의 줄이 있고 (scrollHeight)
우리가 보이는건 1m 이고 (offsetHeight)
지금 있는 높이는 1.3m 라고 생각해봅시다(scrollTop)
스크롤 다운을 해서 (맨 위 0, 내려갈수록 scorollTop up) scrollTop이 2m 가 되면 3m 짜리의 바닥에 닿은 것입니다.

=> (scrollHeight <= offsetHeight + scrollTop) refetch()
이해완료

profile
주경야독

0개의 댓글