웹사이트 검색

React에서 무한 스크롤을 구현하는 방법


소개

무한 스크롤은 사용자가 페이지 하단에 도달하고 새 콘텐츠를 가져오고 로드하여 사용자가 비교적 원활한 환경에서 계속 스크롤할 수 있도록 하는 것입니다. 이것은 번호가 매겨진 페이지나 더 많은 콘텐츠를 로드하는 버튼을 사용하는 다른 페이지 매김 솔루션의 대안입니다.

Instagram과 같은 응용 프로그램에서 무한 스크롤을 경험했을 수 있습니다. 이미지 피드가 표시되고 아래로 스크롤하면 더 많은 이미지가 계속 표시됩니다. 그들이 당신에게 줄 콘텐츠가 다 떨어질 때까지 계속해서.

이 자습서에서는 무한 스크롤이 작동하도록 허용하는 두 가지 주요 개념, 즉 사용자가 페이지 하단에 도달한 시점을 감지하고 표시할 다음 콘텐츠 배치를 로드하는 방법을 다룹니다. 이러한 개념을 사용하여 천문학 사진 및 비디오 디스플레이를 구성합니다.

전제 조건

이 자습서를 완료하려면 다음이 필요합니다.

  • Node.js용 로컬 개발 환경입니다. Node.js 설치 및 로컬 개발 환경 생성 방법을 따르십시오.
  • 이 튜토리얼은 NASA의 APOD(Astronomy Picture of the Day) API를 활용합니다. 데모 목적으로 요청에 DEMO_KEY를 사용하지만 많은 요청을 생성하는 경우 API 키에 가입할 수 있습니다.

이 튜토리얼은 Node v14.12.0, npm v6.14.8, react v16.13.1, superagent v6.1.0 및 에서 확인되었습니다. lodash.debounce v2.7.1.

1단계 - 프로젝트 설정

create-react-app을 사용하여 React 앱을 생성한 다음 종속 항목을 설치하는 것으로 시작합니다.

  1. npx create-react-app react-infinite-scroll-example

새 프로젝트 디렉터리로 변경합니다.

  1. cd react-infinite-scroll-example

APOD API에서 데이터를 로드하려면 superagent를 사용합니다.

이벤트 디바운싱을 위해 lodash를 사용하게 됩니다.

npm을 통해 프로젝트에 superagentlodash.debounce를 추가하려면 다음을 실행합니다.

  1. npm install superagent@6.1.0 lodash.debounce@4.0.8

이제 React 애플리케이션을 실행할 수 있습니다.

  1. npm start

프로젝트의 오류나 문제를 수정하십시오. 그리고 웹 브라우저에서 localhost:3000을 방문하십시오.

작동하는 React 애플리케이션이 있으면 무한 스크롤 기능 구축을 시작할 수 있습니다.

2단계 - onscroll 및 loadApods 구현

무한 스크롤에는 두 가지 핵심 부분이 필요합니다. 한 부분은 사용자가 페이지 하단에 도달했는지 확인하기 위해 창 스크롤 위치와 창 높이를 확인하는 것입니다. 또 다른 부분은 표시할 추가 정보에 대한 요청을 처리하는 것입니다.

InfiniteSpace.js 파일을 생성하여 시작하겠습니다.

  1. nano src/InfiniteSpace.js

InfiniteSpace 구성요소 구성:

import React from 'react';
import request from 'superagent';
import debounce from 'lodash.debounce';

class InfiniteSpace extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      apods: [],
    };
  }

  render() {
    return (
      <div>
        <h1>Infinite Space!</h1>
        <p>Scroll down to load more!!</p>
      </div>
    )
  }
}

export default InfiniteSpace;

무한 스크롤 구성 요소의 핵심은 사용자가 페이지 하단으로 스크롤했는지 확인하는 onscroll 이벤트가 될 것입니다. 페이지 하단에 도달하면 이벤트에서 추가 콘텐츠 로드를 시도합니다.

이벤트를 바인딩할 때, 특히 이벤트를 스크롤할 때 이벤트를 디바운스하는 것이 좋습니다. 디바운싱은 함수가 마지막으로 호출된 후 지정된 시간이 경과한 후에만 함수를 실행하는 것입니다.

디바운싱은 이벤트 발생 빈도를 제한하여 사용자의 성능을 향상시키고 이벤트 핸들러에서 호출할 수 있는 모든 서비스의 부담을 덜어줍니다.

class InfiniteSpace extends Component {
  constructor(props) {
    super(props);

    this.state = {
      apods: [],
    };

    window.onscroll = debounce(() => {
      const {
        loadApods
      } = this;

      if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) {
        loadApods();
      }
    }, 100);
  }

  // ...
}

이 코드는 100밀리초의 디바운스 반복을 설정합니다.

loadApods 기능은 superagent요청을 사용하여 오늘의 천문 사진을 GET합니다.

class InfiniteSpace extends Component {
  constructor(props) {
    // ...
  }

  dayOffset = () => {
    let today = new Date();
    let day = today.setDate(-1 * this.state.apods.length);
    return new Date(day).toISOString().split('T')[0];
  }

  loadApods = () => {
    request
      .get('https://api.nasa.gov/planetary/apod?date=' + this.dayOffset() + '&api_key=DEMO_KEY')
      .then((results) => {
        const nextApod = {
          date: results.body.date,
          title: results.body.title,
          explanation: results.body.explanation,
          copyright: results.body.copyright,
          media_type: results.body.media_type,
          url: results.body.url
        };

        this.setState({
          apods: [
            ...this.state.apods,
            nextApod
          ]
        });
      });
  }

  render() {
    // ...
  }
}

dayOffset 함수는 이전 오늘의 천문 사진을 계산하는 데 사용됩니다.

이 코드는 APOD의 응답을 날짜, 제목, 설명, 저작권, media_type 및 url.

로드된 데이터는 구성 요소 상태의 배열에 추가되고 구성 요소의 render 메서드에서 반복됩니다.

두 조각이 함께 작동하는지 확인하기 위해 응답을 렌더링해 보겠습니다.

class InfiniteSpace extends Component {
  // ...

  render() {
    return(
      <div>
        <h1>Infinite Space!</h1>
        <p>Scroll down to load more!!</p>

        {apods.map(apod => (
          <React.Fragment key={apod.date}>
            <hr />
            <div>
              <h2>{apod.title}</h2>
              {apod.media_type === 'image' &&
                <img
                  alt={`NASA APOD for {apod.date}`}
                  src={apod.url}
                  style={{
                    maxWidth: '100%',
                    height: 'auto'
                  }}
                />
              }
              {apod.media_type === 'video' &&
                <iframe
                  src={apod.url}
                  width='640'
                  height='360'
                  style={{
                    maxWidth: '100%'
                  }}
                ></iframe>
              }
              <div>{apod.explanation}</div>
              <div>{apod.copyright}</div>
            </div>
          </React.Fragment>
        ))}

        <hr />
      </div>
    );
  }
}

이 코드는 APOD의 media_type에 따라 img 또는 iframe을 표시합니다.

이 시점에서 InfiniteSpace를 가져오도록 App 구성 요소를 수정할 수 있습니다. App.js를 엽니다.

  1. nano src/App.js

그리고 Create React App에서 생성된 콘텐츠를 InfiniteSpace 구성 요소로 바꿉니다.

import React from 'react';
import InfiniteSpace from './InfiniteSpace';

function App() {
  return (
    <div className="App">
      <InfiniteSpace />
    </div>
  );
}

export default App;

이 시점에서 애플리케이션을 다시 실행할 수 있습니다.

  1. npm start

프로젝트의 오류나 문제를 수정하십시오. 그리고 웹 브라우저에서 localhost:3000을 방문하십시오.

웹 페이지의 높이를 아래로 스크롤하면 onscroll 이벤트에 대한 조건을 트리거하여 loadApods를 실행하고 새 APOD가 화면에 나타나야 합니다.

무한 스크롤을 위한 이 두 부분으로 InfiniteSpace 구성 요소의 대부분을 설정했습니다. 초기 로드 및 오류 처리를 추가하면 더 견고해집니다.

3단계 - 초기 로드 및 오류 처리 추가

현재 InfiniteSpaceonscroll 이벤트에 대한 조건이 충족될 때까지 APOD를 로드하지 않습니다. 또한 APOD를 로드하지 않으려는 세 가지 상황이 있습니다. 더 이상 로드할 APOD가 없는 경우, 현재 APOD를 로드하는 중이고 오류가 발생하는 경우입니다. 이러한 문제를 해결해 보겠습니다.

먼저 InfiniteSpace.js를 다시 방문하세요.

  1. nano src/InfiniteSpace.js

그런 다음 초기 로드에 componentDidMount()를 사용합니다.

class InfiniteSpace extends Component {
  constructor(props) {
    // ...
  }

  componentDidMount() {
    this.loadApods();
  }

  dayOffset = () => {
    // ...
  }

  loadApods = () => {
    // ...
  }

  render() {
    // ...
  }
}

error, hasMoreisLoading을 상태에 추가하여 오류를 해결하고 불필요한 로드를 제한합니다.

class InfiniteSpace extends Component {
  constructor(props) {
    super(props);

    this.state = {
      error: false,
      hasMore: true,
      isLoading: false,
      apods: []
    };

    // ...
  }

  // ...
}

error는 초기에 false로 설정됩니다. hasMore는 초기에 true로 설정됩니다. 그리고 isLoading은 초기에 false로 설정됩니다.

그런 다음 onscroll에 상태를 적용합니다.

class InfiniteSpace extends Component {
  constructor(props) {
    super(props);

    this.state = {
      error: false,
      hasMore: true,
      isLoading: false,
      apods: []
    };

    window.onscroll = debounce(() => {
      const {
        loadApods,
        state: {
          error,
          isLoading,
          hasMore
        }
      } = this;

      if (error || isLoading || !hasMore) return;

      if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) {
        loadApods();
      }
    }, 100);
  }

  // ...
}

이 검사는 오류가 있거나 현재 로드 중이거나 로드할 추가 APOD가 없는 상황에서 loadApods가 호출되는 것을 조기에 중단하고 방지합니다.

그런 다음 loadApods에 상태를 적용합니다.

class InfiniteSpace extends Component {
  // ...
  
  loadApods = () => { this.setState({ isLoading: true }, () => {
    request
      .get('https://api.nasa.gov/planetary/apod?date=' + this.dayOffset() + '&api_key=DEMO_KEY')
      .then((results) => {
        const nextApod = {
          date: results.body.date,
          title: results.body.title,
          explanation: results.body.explanation,
          copyright: results.body.copyright,
          media_type: results.body.media_type,
          url: results.body.url
        };

        this.setState({
          hasMore: (this.state.apods.length < 5),
          isLoading: false,
          apods: [
            ...this.state.apods,
            nextApod
          ],
        });
      })
      .catch((err) => {
        this.setState({
          error: err.message,
          isLoading: false
          });
      });
    });
  }

  // ...
}

이 코드는 두 번째 인수로 전달된 콜백 함수와 함께 setState를 사용합니다. loadApods 메서드에서 setState에 대한 초기 호출은 isLoading의 값을 true로 설정한 다음 콜백 함수에서 설정합니다. 다음 APOD가 로드되고 setState가 다시 호출되어 isLoadingfalse로 설정합니다.

튜토리얼의 목적상 hasMore는 APOD의 양을 5로 제한하는 부울 검사입니다. 다양한 시나리오에서 API는 로드할 콘텐츠가 더 있는지 여부를 나타내는 페이로드의 일부로 일부 값을 반환할 수 있습니다.

loadApods에 오류가 발생하면 catch 블록에서 errorerr.message로 설정됩니다.

그런 다음 상태를 렌더링에 적용합니다.

class InfiniteSpace extends Component {
  // ...

  render() {
    const {
      error,
      hasMore,
      isLoading,
      apods
    } = this.state;

    return (
      <div>
        {/* ... React.Fragment ... */}

        {error &&
          <div style={{ color: '#900' }}>
            {error}
          </div>
        }

        {isLoading &&
          <div>Loading...</div>
        }

        {!hasMore &&
          <div>Loading Complete</div>
        }
      </div>
    );
  }
]

이제 error, isLoadinghasMore에 대한 메시지가 표시됩니다.

모든 조각이 합쳐지면 InfiniteSpace는 다음과 같이 표시됩니다.

import React from 'react';
import request from 'superagent';
import debounce from 'lodash.debounce';

class InfiniteSpace extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      error: false,
      hasMore: true,
      isLoading: false,
      apods: []
    };

    window.onscroll = debounce(() => {
      const {
        loadApods,
        state: {
          error,
          isLoading,
          hasMore,
        },
      } = this;

      if (error || isLoading || !hasMore) return;

      if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) {
        loadApods();
      }
    }, 100);
  }

  componentDidMount() {
    this.loadApods();
  }

  dayOffset = () => {
    let today = new Date();
    let day = today.setDate(-1 * this.state.apods.length);
    return new Date(day).toISOString().split('T')[0];
  }

  loadApods = () => {this.setState({ isLoading: true }, () => {
    request
      .get('https://api.nasa.gov/planetary/apod?date=' + this.dayOffset() + '&api_key=DEMO_KEY')
      .then((results) => {
        const nextApod = {
          date: results.body.date,
          title: results.body.title,
          explanation: results.body.explanation,
          copyright: results.body.copyright,
          media_type: results.body.media_type,
          url: results.body.url
        };

        this.setState({
          hasMore: (this.state.apods.length < 5),
          isLoading: false,
          apods: [
            ...this.state.apods,
            nextApod
          ],
        });
      })
      .catch((err) => {
        this.setState({
          error: err.message,
          isLoading: false
        });
      });
    });
  }

  render() {
    const {
      error,
      hasMore,
      isLoading,
      apods
    } = this.state;

    return (
      <div style={{
        padding: 10
      }}>
        <h1>Infinite Space!</h1>
        <p>Scroll down to load more!!</p>

        {apods.map(apod => (
          <React.Fragment key={apod.date}>
            <hr />
            <div>
              <h2>{apod.title}</h2>
              {apod.media_type === 'image' &&
                <img
                  alt={`NASA APOD for {apod.date}`}
                  src={apod.url}
                  style={{
                    maxWidth: '100%',
                    height: 'auto'
                  }}
                />
              }
              {apod.media_type === 'video' &&
                <iframe
                  src={apod.url}
                  width='640'
                  height='360'
                  style={{
                    maxWidth: '100%'
                  }}
                ></iframe>
              }
              <div>{apod.explanation}</div>
              <div>{apod.copyright}</div>
            </div>
          </React.Fragment>
        ))}

        <hr />

        {error &&
          <div style={{ color: '#900' }}>
            {error}
          </div>
        }

        {isLoading &&
          <div>Loading...</div>
        }

        {!hasMore &&
          <div>Loading Complete</div>
        }
      </div>
    );
  }
}

export default InfiniteSpace;

마지막으로 애플리케이션을 다시 실행합니다.

  1. npm start

프로젝트의 오류나 문제를 수정하십시오. 그리고 웹 브라우저에서 localhost:3000을 방문하십시오.

아래로 스크롤하면 애플리케이션이 5개의 APOD를 가져와서 표시합니다. 무한 스크롤을 위한 모든 조각이 모였습니다.

결론

이 자습서에서는 React 애플리케이션에서 무한 스크롤을 구현했습니다. 무한 스크롤은 많은 초기 로딩 시간 없이 최종 사용자에게 많은 정보를 제공할 수 있는 현대적인 솔루션 중 하나입니다.

프로젝트에 사용자가 도달하기를 원하는 페이지 바닥글에 콘텐츠가 있는 경우 무한 스크롤로 인해 사용자 환경이 악화될 수 있습니다.

프로젝트의 요구에 가장 적합할 수 있는 이 기능을 제공하는 다른 라이브러리도 있습니다.

React에 대해 자세히 알아보려면 연습 및 프로그래밍 프로젝트에 대한 React 주제 페이지를 살펴보세요.