React로 무한 스크롤 구현하는 방법

React로 무한 스크롤 구현하는 방법

게시판, 포럼 등 내용이 많은 페이지에 대해서 스크롤이 길어질 때 페이지를 새로 고치지 않고, 한 페이지에서 모든 내용을 볼 수 있게 해주는 무한 스크롤 기능이 최신의 웹개발 트렌드인 것 같습니다. 웹개발에 많이 사용되는 리액트에서 무한 스크롤 기능을 구현하는 방법은 여러 가지가 있는데, 이 글에서는 세 가지 방법에 대해서 알아보겠습니다.

먼저, 리액트로 무한 스크롤을 구현하는 구체적인 방법은 아래와 같습니다.

리액트에서 무한 스크롤 구현하기

1. 초기 데이터 로딩: 페이지가 처음 로드될 때 초기 데이터를 가져와야 합니다. 이를 위해서는 API 요청 등을 사용하여 초기 데이터를 가져옵니다.

2. 스크롤 이벤트 감지: 무한 스크롤을 구현하려면 스크롤 이벤트를 감지해야 합니다. 이를 위해서는 window.addEventListener 함수를 사용하여 스크롤 이벤트를 등록합니다.

  componentDidMount() {
    window.addEventListener('scroll', this.handleScroll);
  }

  componentWillUnmount() {
    window.removeEventListener('scroll', this.handleScroll);
  }

  handleScroll = () => {
    const { loading, hasMore } = this.props;
    if (loading || !hasMore) return;
    if (
      window.innerHeight + document.documentElement.scrollTop ===
      document.documentElement.offsetHeight
    ) {
      this.loadMore();
    }
  };

위의 코드에서 loading은 현재 데이터를 로딩 중인지를 나타내는 상태 값입니다. hasMore는 더 많은 데이터가 있는지 나타내는 상태 값입니다. 스크롤 이벤트를 처리하는 handleScroll 메소드는 스크롤 위치를 계산하여 더 많은 데이터를 로드하는 loadMore 메소드를 호출합니다.

3. 더 많은 데이터 로딩: 스크롤 이벤트가 발생하면 loadMore 메소드를 호출하여 더 많은 데이터를 가져와야 합니다. 이를 위해 API 요청 등을 사용하여 새로운 데이터를 가져옵니다. 가져온 데이터는 이전 데이터와 병합하여 화면에 렌더링합니다.

  loadMore = () => {
    const { loadMore, page } = this.props;
    loadMore(page + 1);
  };

위의 코드에서 loadMore는 새로운 데이터를 로드하는 메소드입니다. page는 현재 페이지를 나타내는 상태 값입니다. loadMore 함수는 새로운 페이지를 인수로 받아 API 요청 등을 사용하여 새로운 데이터를 가져옵니다.

4. 데이터 렌더링: 가져온 데이터를 이전 데이터와 병합하여 화면에 렌더링합니다. 이를 위해 map 메소드 등을 사용하여 데이터를 반복하고, 각 데이터를 컴포넌트로 변환하여 렌더링합니다.

  render() {
    const { data } = this.props;
    return (
      <div>
        {data.map((item) => (
          <Item key={item.id} item={item} />
        ))}
      </div>
    );
  }

위 코드에서 data는 현재까지 로드된 모든 데이터를 나타내는 상태 값입니다. Item 컴포넌트는 각 데이터를 나타내는 컴포넌트입니다. map 메소드를 사용하여 data 배열을 반복하면서 Item 컴포넌트를 생성합니다. 각각의 Item 컴포넌트는 item prop으로 현재 데이터를 전달받습니다.

5. 로딩 상태 표시: 새로운 데이터를 로딩하는 동안 사용자에게 로딩 상태를 표시해야 합니다. 이를 위해 로딩 중임을 나타내는 UI를 렌더링합니다.

  render() {
    const { data, loading } = this.props;
    return (
      <div>
        {data.map((item) => (
          <Item key={item.id} item={item} />
        ))}
        {loading && <div>Loading...</div>}
      </div>
    );
  }

위 코드에서 loading은 현재 데이터를 로딩 중인지 여부를 나타내는 상태 값입니다. loading이 true인 경우, Loading… 메시지를 화면에 렌더링합니다.

6. 더 이상 데이터가 없음을 표시: 추가로 로드할 데이터가 없는 경우 사용자에게 표시해야 합니다. 이를 위해 hasMore 상태 값을 사용하여 더 이상 로드할 데이터가 없는 경우 UI를 렌더링합니다.

  render() {
    const { data, loading, hasMore } = this.props;
    return (
      <div>
        {data.map((item) => (
          <Item key={item.id} item={item} />
        ))}
        {loading && <div>Loading...</div>}
        {!loading && !hasMore && <div>No more data</div>}
      </div>
    );
  }

위 코드에서 hasMore는 추가로 로드할 데이터가 있는지 나타내는 상태 값입니다. hasMore가 false인 경우 No more data 메시지를 화면에 렌더링합니다.

7. 추가적인 최적화: 위 코드로 무한 스크롤 작업이 진행되는 동안 불필요한 데이터 요청 등이 발생할 수 있습니다. 이를 해결하기 위해서는 스크롤 이벤트 등을 디바운스 처리하거나, 데이터를 캐싱하는 등 추가적인 최적화 작업이 필요할 수도 있습니다.

다음으로, 리액트로 무한 스크롤을 구현하는 다른 방법에 대해서도 알아보겠습니다. 이 글에서는 IntersectionObserver API를 사용하는 방법, react-infinite-scroll-component 라이브러리를 사용하는 방법, Server-Side Rendering (SSR)을 활용하는 방법에 대해서 알아보겠습니다.

IntersectionObserver API 사용 방법

리액트에서 IntersectionObserver API를 사용하여 무한 스크롤을 구현하는 방법은 아래와 같습니다.

1. IntersectionObserver 객체 생성: IntersectionObserver API는 IntersectionObserver 객체를 생성하여 뷰포트 내의 요소와 상호작용할 때 사용됩니다. IntersectionObserver 객체를 생성할 때는 Callback 함수를 등록해야 합니다. 이 Callback 함수는 뷰포트 내의 요소와 교차할 때마다 호출됩니다.

componentDidMount() {
  const options = {
    root: null,
    rootMargin: '0px',
    threshold: 0.1
  };

  this.observer = new IntersectionObserver(this.handleObserver, options);
}

위 코드에서 options는 IntersectionObserver 객체를 생성할 때 사용하는 옵션 객체입니다. root는 뷰포트를 기준으로 교차 검사를 할 요소를 지정하는데, null을 지정하면 뷰포트를 기준으로 교차 검사를 합니다. rootMargin은 교차 영역의 마진 값을 설정하고, threshold는 교차 영역의 비율을 나타냅니다.

2. 요소 감시: IntersectionObserver 객체를 사용하여 감시할 요소를 등록합니다. 이를 위해서는 observe 메소드를 사용합니다. 등록된 요소는 뷰포트 내의 요소와 교차할 때마다 콜백 함수가 호출됩니다.

componentDidMount() {
  const options = {
    root: null,
    rootMargin: '0px',
    threshold: 0.1
  };

  this.observer = new IntersectionObserver(this.handleObserver, options);

  if (this.loadingRef.current) {
    this.observer.observe(this.loadingRef.current);
  }
}

위 코드에서 loadingRef는 무한 스크롤을 위한 로딩 요소의 ref 객체입니다. observe 메소드를 사용하여 loadingRef를 등록하면 loadingRef 요소가 뷰포트 내에 진입할 때마다 콜백 함수가 호출됩니다.

3. 콜백 함수 처리: IntersectionObserver 객체 콜백 함수에서는 뷰포트 내의 요소와 교차한 것을 확인하고, 새로운 데이터를 가져오는 작업을 수행합니다.

handleObserver = (entries, observer) => {
  const { loading, hasMore } = this.props;

  if (loading || !hasMore) return;

  entries.forEach(entry => {
    if (entry.isIntersecting) {
      this.loadMore();
      observer.unobserve(entry.target);
    }
  });
};

위 코드에서 handleObserver 함수는 IntersectionObserver 객체의 콜백 함수입니다. entries는 뷰포트 내의 요소와 교차한 정보를 담고 있는 배열입니다. observer는 IntersectionObserver 객체를 참조하는데, 이 객체를 사용하여 요소 감시를 중지할 수 있습니다.

entries 배열을 반복하면서 isIntersecting 속성이 true인 요소를 찾으면, loadMore 함수를 호출하여 새로운 데이터를 가져옵니다. 가져온 데이터는 이전 데이터와 병합하여 화면에 렌더링합니다. 새로운 데이터를 가져온 후 observer 객체의 unobserve 메서드를 사용하여 감시 대상에서 제외합니다.

4. 컴포넌트 언마운트 시 요소 감시 중지: IntersectionObserver 객체는 컴포넌트가 언마운트될 때 반드시 중지해야 합니다. 이를 위해 componentWillUnmount 메소드에서 disconnect 메소드를 호출하여 감시를 중지합니다.

componentWillUnmount() {
  if (this.observer) {
    this.observer.disconnect();
  }
}

위 코드에서 disconnect 메소드는 IntersectionObserver 객체에서 요소 감시를 중지하는 메소드입니다. 지금까지 알아본 내용으로, IntersectionObserver API를 사용하여 리액트로 무한 스크롤을 구현할 수 있습니다.

react-infinite-scroll-component 사용 방법

리액트에서 react-infinite-scroll-component를 사용하여 무한 스크롤을 구현하는 방법은 아래와 같습니다.

1. 라이브러리 설치: 먼저 react-infinite-scroll-component 라이브러리를 설치해야 합니다. 이를 위해, npm 또는 yarn을 사용하여 라이브러리를 설치합니다.

npm install --save react-infinite-scroll-component

2. InfiniteScroll 컴포넌트 사용: react-infinite-scroll-component 라이브러리에서는 InfiniteScroll 컴포넌트를 제공합니다. 이를 사용하여 무한 스크롤을 구현할 수 있습니다.

import InfiniteScroll from 'react-infinite-scroll-component';

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

  fetchMoreData = () => {
    // 새로운 데이터를 가져와서 state 업데이트
  };

  render() {
    return (
      <InfiniteScroll
        dataLength={this.state.items.length}
        next={this.fetchMoreData}
        hasMore={true}
        loader={<h4>Loading...</h4>}
      >
        {this.state.items.map((item, index) => (
          <div key={index}>{item}</div>
        ))}
      </InfiniteScroll>
    );
  }
}

위 코드에서 InfiniteScroll 컴포넌트는 next prop을 통해 fetchMoreData 함수를 호출하여 더 많은 데이터를 가져옵니다. hasMore prop은 더 이상 데이터가 없는지 나타냅니다. loader prop은 데이터를 가져오는 동안 표시할 로딩 UI입니다.

3. 데이터 로딩 함수 작성: next prop으로 전달된 fetchMoreData 함수에서는 새로운 데이터를 가져와서 state를 업데이트합니다.

fetchMoreData = () => {
  // 새로운 데이터를 가져와서 state 업데이트
  const newItems = Array.from({ length: 20 });
  this.setState({
    items: [...this.state.items, ...newItems]
  });
};

위 코드에서는 Array.from 메소드를 사용하여 임의의 데이터를 생성하고, 기존 데이터와 병합하여 state를 업데이트합니다.

무한 스크롤 최적화: 무한 스크롤을 구현하는 동안 불필요한 데이터 요청 등이 발생할 수 있습니다. 이를 최적화하려면 InfiniteScroll 컴포넌트에서 제공하는 scrollThreshold prop 등을 활용하여 스크롤 이벤트를 디바운스 처리하거나, 데이터를 캐싱하는 등의 추가적인 최적화 작업을 수행할 수 있습니다.

<InfiniteScroll
  dataLength={this.state.items.length}
  next={this.fetchMoreData}
  hasMore={true}
  loader={<h4>Loading...</h4>}
  scrollThreshold={0.5}
>
  {this.state.items.map((item, index) => (
    <div key={index}>{item}</div>
  ))}
</InfiniteScroll>

위 코드에서 scrollThreshold는 무한 스크롤을 구현할 때 언제 데이터를 로드할지 결정하는 기준값을 나타냅니다. scrollThreshold 값이 0.5인 경우 스크롤이 뷰포트 하단의 50% 지점에 도달했을 때 새로운 데이터를 로드한다는 것을 의미합니다.

Server-Side Rendering (SSR) 활용 방법

리액트에서 Server-Side Rendering (SSR)을 사용하여 무한 스크롤을 구현하는 방법은 다음과 같습니다.

1. 서버사이드 렌더링 모듈 설치: 먼저, 서버사이드 렌더링 모듈을 설치해야 합니다. 여기서는 react-dom/server 모듈을 설치합니다.

npm install --save react react-dom react-dom/server

2. 서버사이드 렌더링 함수 작성: 서버사이드 렌더링 함수에서는 초기 데이터를 로드하고, 초기 렌더링 결과를 반환합니다.

import React from 'react';
import ReactDOMServer from 'react-dom/server';

import App from './App';

const renderApp = (initialData) => {
  const app = ReactDOMServer.renderToString(<App initialData={initialData} />);

  return `<!doctype html>
    <html>
      <head>
        <title>My App</title>
      </head>
      <body>
        <div id="root">${app}</div>
        <script>
          window.__INITIAL_DATA__ = ${JSON.stringify(initialData)};
        </script>
        <script src="/bundle.js"></script>
      </body>
    </html>`;
};

위 코드에서 renderApp 함수는 ReactDOMServer.renderToString 메소드를 사용하여 초기 렌더링 결과를 문자열로 반환합니다. 이 결과를 HTML로 렌더링하고, 초기 데이터를 window.__INITIAL_DATA__ 객체에 저장하여 클라이언트에서 사용할 수 있도록 합니다. bundle.js는 클라이언트에서 사용되는 자바스크립트 번들 파일입니다.

3. 초기 데이터 로드: 서버사이드 렌더링 함수에서 초기 데이터를 로드해야 합니다. 이를 위해 fetch API를 사용하여 서버에서 데이터를 가져옵니다.

import React from 'react';
import ReactDOMServer from 'react-dom/server';
import fetch from 'node-fetch';

import App from './App';

const renderApp = async () => {
  const response = await fetch('https://my-api.com/data');
  const initialData = await response.json();
  const app = ReactDOMServer.renderToString(<App initialData={initialData} />);

  return `<!doctype html>
    <html>
      <head>
        <title>My App</title>
      </head>
      <body>
        <div id="root">${app}</div>
        <script>
          window.__INITIAL_DATA__ = ${JSON.stringify(initialData)};
        </script>
        <script src="/bundle.js"></script>
      </body>
    </html>`;
};

위 코드에서 fetch 함수를 사용하여 https://my-api.com/data(예시)에서 데이터를 가져옵니다. 이 데이터는 initialData 객체에 저장되며, 이를 <App> 컴포넌트의 initialData prop으로 전달합니다.

4. 서버사이드 렌더링 실행: 서버사이드 렌더링 함수를 실행하여 초기 렌더링 결과를 반환합니다. 이를 위해서는 서버에서 HTTP 요청을 처리할 때 renderApp 함수를 호출합니다.

import http from 'http';

const server = http.createServer(async (req, res) => {
  const html = await renderApp();
  res.writeHead(200, { 'Content-Type': 'text/html' });
  res.end(html);
});

server.listen(3000);

위 코드에서 http 모듈을 사용하여 서버를 생성하고, 요청이 들어올 때마다 renderApp 함수를 호출하여 HTML을 생성합니다. 이 HTML을 응답으로 반환합니다.

5. 클라이언트 사이드 렌더링: 클라이언트에서는 서버에서 전달받은 초기 데이터를 사용하여 렌더링을 수행합니다. 이때, 무한 스크롤을 구현하기 위해 IntersectionObserver API나 react-infinite-scroll-component 라이브러리를 사용하여 클라이언트에서 무한 스크롤을 구현합니다.

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

const initialData = window.__INITIAL_DATA__;

ReactDOM.hydrate(<App initialData={initialData} />, document.getElementById('root'));

위 코드에서 ReactDOM.hydrate 함수를 사용하여 서버에서 전달받은 초기 데이터를 사용하여 클라이언트에서 렌더링을 수행합니다. initialData는 window.__INITIAL_DATA__ 객체에서 가져옵니다.


지금까지 리액트에서 무한 스크롤 기능을 구현하는 방법에 대해서 알아봤습니다.

Leave a reply

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다