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 앱을 생성한 다음 종속 항목을 설치하는 것으로 시작합니다.
- npx create-react-app react-infinite-scroll-example
새 프로젝트 디렉터리로 변경합니다.
- cd react-infinite-scroll-example
APOD API에서 데이터를 로드하려면 superagent
를 사용합니다.
이벤트 디바운싱을 위해 lodash
를 사용하게 됩니다.
npm
을 통해 프로젝트에 superagent
및 lodash.debounce
를 추가하려면 다음을 실행합니다.
- npm install superagent@6.1.0 lodash.debounce@4.0.8
이제 React 애플리케이션을 실행할 수 있습니다.
- npm start
프로젝트의 오류나 문제를 수정하십시오. 그리고 웹 브라우저에서 localhost:3000
을 방문하십시오.
작동하는 React 애플리케이션이 있으면 무한 스크롤 기능 구축을 시작할 수 있습니다.
2단계 - onscroll 및 loadApods 구현
무한 스크롤에는 두 가지 핵심 부분이 필요합니다. 한 부분은 사용자가 페이지 하단에 도달했는지 확인하기 위해 창 스크롤 위치와 창 높이를 확인하는 것입니다. 또 다른 부분은 표시할 추가 정보에 대한 요청을 처리하는 것입니다.
InfiniteSpace.js
파일을 생성하여 시작하겠습니다.
- 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
를 엽니다.
- 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;
이 시점에서 애플리케이션을 다시 실행할 수 있습니다.
- npm start
프로젝트의 오류나 문제를 수정하십시오. 그리고 웹 브라우저에서 localhost:3000
을 방문하십시오.
웹 페이지의 높이를 아래로 스크롤하면 onscroll
이벤트에 대한 조건을 트리거하여 loadApods
를 실행하고 새 APOD가 화면에 나타나야 합니다.
무한 스크롤을 위한 이 두 부분으로 InfiniteSpace
구성 요소의 대부분을 설정했습니다. 초기 로드 및 오류 처리를 추가하면 더 견고해집니다.
3단계 - 초기 로드 및 오류 처리 추가
현재 InfiniteSpace
는 onscroll
이벤트에 대한 조건이 충족될 때까지 APOD를 로드하지 않습니다. 또한 APOD를 로드하지 않으려는 세 가지 상황이 있습니다. 더 이상 로드할 APOD가 없는 경우, 현재 APOD를 로드하는 중이고 오류가 발생하는 경우입니다. 이러한 문제를 해결해 보겠습니다.
먼저 InfiniteSpace.js
를 다시 방문하세요.
- nano src/InfiniteSpace.js
그런 다음 초기 로드에 componentDidMount()
를 사용합니다.
class InfiniteSpace extends Component {
constructor(props) {
// ...
}
componentDidMount() {
this.loadApods();
}
dayOffset = () => {
// ...
}
loadApods = () => {
// ...
}
render() {
// ...
}
}
error
, hasMore
및 isLoading
을 상태에 추가하여 오류를 해결하고 불필요한 로드를 제한합니다.
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
가 다시 호출되어 isLoading
을 false
로 설정합니다.
튜토리얼의 목적상 hasMore
는 APOD의 양을 5
로 제한하는 부울 검사입니다. 다양한 시나리오에서 API는 로드할 콘텐츠가 더 있는지 여부를 나타내는 페이로드의 일부로 일부 값을 반환할 수 있습니다.
loadApods
에 오류가 발생하면 catch
블록에서 error
가 err.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
, isLoading
및 hasMore
에 대한 메시지가 표시됩니다.
모든 조각이 합쳐지면 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;
마지막으로 애플리케이션을 다시 실행합니다.
- npm start
프로젝트의 오류나 문제를 수정하십시오. 그리고 웹 브라우저에서 localhost:3000
을 방문하십시오.
아래로 스크롤하면 애플리케이션이 5개의 APOD를 가져와서 표시합니다. 무한 스크롤을 위한 모든 조각이 모였습니다.
결론
이 자습서에서는 React 애플리케이션에서 무한 스크롤을 구현했습니다. 무한 스크롤은 많은 초기 로딩 시간 없이 최종 사용자에게 많은 정보를 제공할 수 있는 현대적인 솔루션 중 하나입니다.
프로젝트에 사용자가 도달하기를 원하는 페이지 바닥글에 콘텐츠가 있는 경우 무한 스크롤로 인해 사용자 환경이 악화될 수 있습니다.
프로젝트의 요구에 가장 적합할 수 있는 이 기능을 제공하는 다른 라이브러리도 있습니다.
React에 대해 자세히 알아보려면 연습 및 프로그래밍 프로젝트에 대한 React 주제 페이지를 살펴보세요.