본문 바로가기

코드스테이츠 SEB FE 41기/Main-Project(MatP)

[react & typescript] 무한스크롤 구현하기(feat. 팀원 분)

반응형

도메인 페이지에 나오는 '오늘의 맛포스트' 컨테이너를 무한 스크롤로 구현한 부분을 정리해보자. 도메인의 ui나 데이터 실시간 렌더링 등은 내가 맡아서 하였지만 무한 스크롤 기능은 다른 팀원분이 하셔서 프로젝트 종료 이후 코드를 보고 구글링을 해가며 따로 공부했다.

왠지 무한 스크롤은 나중에 분명 다시 찾을 일이 있을 것 같기 때문에! 이번 기회에 정리를 해보자! :)

무한스크롤로 끝도 없는 맛포스트를 볼 수 있다아아아

먼저 무한 스크롤을 구현하기 위해 알아야 할 개념에 대해 간단히 짚고 넘어가자.

  • clientHeight
    현재 화면에서 보이는 높이라고 생각하면 편하다.
  • scrollHeight
    스크롤을 포함한 전체 요소의 높이를 말한다. 스크롤이 생겨서 뷰포트에 보이지 않는 아래의 요소들의 높이까지 전부 포함한다. 
  • scrollTop
    top 속성이므로 세로의 스크롤 속성이 있을때만 작동한다. 현재 스크롤바의 위치라고 생각하면 편하다.


scrollTop과 clientHeight를 더한 값이 scrollHeight 값과 같을 때, 즉 스크롤이 화면 맨 끝에 도달했을 때, 다음 24개의 맛포스트를 렌더링하도록 하면 된다.

이제 이 개념들을 바탕으로 무한 스크롤 로직을 살펴보자.

먼저, 컴포넌트 구조는 이렇다.

 <StyledPosts onScroll={handleScroll}>
	{/* 처음에 렌더링되는 24개의 맛포스트 */}
       {posts &&
          posts.map((post: IPosts) => (
            <PostRead
              key={post.id}
              post={post}
              getAllPostsReload={getAllPostsReload}
            />
        ))}
      {postData &&
          postData.map((post: IPosts) => (
            <PostRead
              key={post.id}
              post={post}
              getAllPostsReload={getAllPostsReload}
            />
       ))}
</StyledPosts>

무한 스크롤 로직을 다른 팀원분이 만드셔서 map 함수를 사용하여 뿌려주는 부분이 두 번 있는 이유는 제대로 파악을 하지 못하였다. 맛포스트 관련 서버 API 요청 설정때문에 그런 것 같기도..

처음에는 가장 최근에 작성된 24개의 맛포스트인 posts에 대한 GET 요청이 이루어져서 response로 받아온 데이터를 먼저 뿌려주고, 그 이후에 스크롤 이벤트 발생시 무한스크롤(페이지네이션)이 적용된 GET 요청을 보내야해서 그런 것 같다(어디까지나 내 뇌피셜)...
무튼 posts 데이터를 뿌려주는 부분을 지우면 처음 컴포넌트가 렌더링되었을 때 아무것도 뜨지 않아 해당 부분을 지워주면 안된다.

export const getPosts = async () => {
  const response = await axios.get(`${url}/places/posts?page=0&size=24`);
  return response.data;
};

export const getPagePosts = async (page: number, limit: number) => {
  const response = await axios.get(`${url}/places/posts?page=${page}&size=${limit}`);
  return response.data;
};

하단의 코드는 무한스크롤 로직 관련 코드이다.

// 맛포스트 데이터 변동 사항 발생 시 실시간 업데이트될 수 있도록 하는 불린 변수(종속성 배열에 포함됨)
const [postsReload, setPostsReload] = useState<boolean>(false);
// 보여줄 맛포스트가 더 있는지/없는지 여부
const [hasMore, setHasMore] = useState<boolean>(true);
// 페이지 번호
const [page, setPage] = useState(1);
// 한 페이지에 24개의 맛포스트 보여줌
const [limit] = useState(24);
// 맛포스트 리스트 데이터
const [postData, setPostData] = useState([]);

// 맛포스트 List GET
const { responseData: posts } = useAxios(getPosts, [postsReload], false);
// 맛포스트 List GET(무한스크롤 ver)
const { axiosData: getPageAxios, responseData: pagePosts } = useAxios(
    () => getPagePosts(page, limit),
    [page, postData],
    false
  );

// 맛포스트 List GET 요청 함수 실행되도록 한 후, 맛포스트 리스트 state update
const loadData = () => {
    getPageAxios();
    setPostData([...postData, ...pagePosts]);
  };

// 무한 스크롤 함수 
const handleScroll = (event: React.UIEvent<HTMLDivElement>) => {
    const target = event.target as HTMLDivElement;
    const { scrollTop, clientHeight, scrollHeight } = target;
    if (scrollTop + clientHeight >= scrollHeight && hasMore) {
      // 페이지 끝에 도달하면 추가 데이터를 받아온다
      setPage(page + 1);
      loadData();
      if (pagePosts.length < limit) {
        setHasMore(false);
      }
    }
};

 

참고한 블로그)

https://kingso.netlify.app/posts/react-infinite-scroll/

https://velog.io/@otterp/%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%97%90%EC%84%9C-%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0

 

반응형