react-infinite-scroll-component

25303 단어 ReactReact

infinite scroll을 예전에 구현했을 땐 직접 구현했었는데 회사에서 라이브러리를 사용했기 때문에 이것에 대한 사용방법을 익히고 다시한번 예전에 구현했던 코드를 상기시키는 시간을 가져보려 한다

react-infinite-scroll-component

// 공식 문서 예시 1
import React from "react";
import { render } from "react-dom";
import InfiniteScroll from "react-infinite-scroll-component";

const style = {
  height: 30,
  border: "1px solid green",
  margin: 6,
  padding: 8
};

class App extends React.Component {
  state = {
    items: Array.from({ length: 20 })
  };

  fetchMoreData = () => {
    // a fake async api call like which sends
    // 20 more records in 1.5 secs
    setTimeout(() => {
      this.setState({
        items: this.state.items.concat(Array.from({ length: 20 }))
      });
    }, 1500);
  };

  render() {
    return (
      <div>
        <h1>demo: react-infinite-scroll-component</h1>
        <hr />
        <InfiniteScroll
          dataLength={this.state.items.length} // 반복되는 컴포넌트의 개수
          next={this.fetchMoreData} // 스크롤이 바닥에 닿으면 데이터를 더 불러오는 함수
          hasMore={true} // 추가 데이터 유무
          loader={<h4>Loading...</h4>} // 로딩스피너
        >
          {this.state.items.map((i, index) => (
            <div style={style} key={index}>
              div - #{index}
            </div>
          ))}
        </InfiniteScroll>
      </div>
    );
  }
}

render(<App />, document.getElementById("root"));

// 공식 문서 예시 2
<div
  id="scrollableDiv"
  style={{
    height: 300,
    overflow: 'auto',
    display: 'flex',
    flexDirection: 'column-reverse',
  }}
>
  {/*Put the scroll bar always on the bottom*/}
  <InfiniteScroll
    dataLength={this.state.items.length}
    next={this.fetchMoreData}
    style={{ display: 'flex', flexDirection: 'column-reverse' }} //To put endMessage and loader to the top.
    inverse={true} //
    hasMore={true}
    loader={<h4>Loading...</h4>}
    scrollableTarget="scrollableDiv"
  >
    {this.state.items.map((_, index) => (
      <div style={style} key={index}>
        div - #{index}
      </div>
    ))}
  </InfiniteScroll>
</div>
  • scrollableTarget : 여러 컨텐츠 리스트에 의해 스크롤이 생기는 엘리먼츠

    • 원하는 엘리먼츠의 id를 입력해서 지정 가능
    • document.body로 설정하려면 scrollableTarget, height 모두 설정하지 않으면 document.body를 기준으로 함
  • 동작 원리 : height prop을 제공하지 않으면 기본적으로 scrollableTarget(document.body)의 맨 끝에 닿으면 next함수가 실행되면서 추가 데이터를 요청하는 구조

기존에 직접 구현하던 방식

browser Web API

  • 1) document.documentElement.scrollTop : 전체화면 중 현재 화면의 위치
  • 2) window.innerHeight : 현재 화면의 전체 높이
  • 3) document.documentElement.scrollHeight : 전체화면의 전체 높이

  • 스크롤을 내리면 현재 화면의 위치가 바뀜(document.documentElement.scrollTop)
  • 스크롤을 최대로 내리면 현재화면의 위치 + 현재화면의 높이 = 전체화면의 높이가 됨
    • 이때 새로운 페이지 데이터를 요청
    • document.documentElement.scrollTop + window.innerHeight + 300 >= document.documentElement.scrollHeight
    • 최대로 내리기 전에 요청을 하면 더 좋은 사용자 경험을 줄 수 있음

구현 예시

// useInfiniteScroll.ts
export function useInfiniteScroll() {
  const [page, setPage] = useState(1);
  function handleScroll() {
    if (
      document.documentElement.scrollTop + window.innerHeight + 300 >=
      document.documentElement.scrollHeight
    ) {
      setPage((p) => p + 1);
    }
  }
  useEffect(() => {
    window.addEventListener("scroll", handleScroll);
  }, []);
  return page;
}


// Movie.tsx
import { movieApi } from "../api/axiosApi";

const Movie = () => {
  const [movies, setMovies] = useState<Movie[]>([]);
  const page = useInfiniteScroll();

  useEffect(() => {
    movieApi
      .nowPlaying(page)
      .then((res) => {
        const {
          data: { results },
        } = res;
        setMovies((prev) => [...prev, ...results]);
      })
      .catch((err) => console.error(err));
  }, [page]);

  return (
      <main>
        {movies.map((movie) => {
          return (
            <div key={movie.id}>
              <h3>{movie.title}</h3>
              <p>{movie.overview}</p>
            </div>
          );
        })}
      </main>
  );
};

export default Movie;
  • 스크롤 이벤트로 인한 페이지 요청 시 한번에 여러번 요청이 될 가능성이 높으므로 로딩 상태에선 요청을 막는 로직이 필요함(예시엔 구현이 안되어 있음)
  • 향후 react-query를 사용하여 loading 동안 요청을 하지 않도록 할 예정

참고

좋은 웹페이지 즐겨찾기